@authhero/cloudflare-adapter 3.2.5 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloudflare-adapter.cjs +1 -1
- package/dist/cloudflare-adapter.mjs +1 -1
- package/dist/{sync-defaults-errors-BlLZh6Gt.mjs → sync-defaults-errors-CKdx0nzD.mjs} +4 -2
- package/dist/{sync-defaults-errors-Htp1NWTq.js → sync-defaults-errors-pnzkP88r.js} +1 -1
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/wfp/dispatch-detach.d.ts +31 -0
- package/dist/types/wfp/dispatch-sync-config.d.ts +37 -0
- package/dist/types/wfp/dispatch-tenant-member-requests.d.ts +23 -0
- package/dist/types/wfp/index.d.ts +3 -0
- package/dist/types/wfp/tenant-app.d.ts +26 -1
- package/dist/types/workflows/detach-operation.d.ts +31 -0
- package/dist/types/workflows/entrypoint.example.d.ts +63 -3
- package/dist/types/workflows/index.d.ts +2 -0
- package/dist/types/workflows/sync-defaults-operation.d.ts +28 -0
- package/dist/types/workflows/types.d.ts +2 -2
- package/dist/wfp.cjs +1 -1
- package/dist/wfp.d.ts +114 -3
- package/dist/wfp.mjs +303 -61
- package/dist/workflows.cjs +1 -1
- package/dist/workflows.d.ts +62 -5
- package/dist/workflows.mjs +145 -41
- package/package.json +5 -5
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { DataAdapters } from "@authhero/adapter-interfaces";
|
|
2
|
+
import { type DetachConfigFn } from "@authhero/multi-tenancy";
|
|
3
|
+
import type { DispatchNamespace } from "../code-executor";
|
|
4
|
+
export interface DispatchDetachOptions {
|
|
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/detach`
|
|
11
|
+
* (`WFP_INTERNAL_SYNC_SECRET`). Sent as a bearer token.
|
|
12
|
+
*/
|
|
13
|
+
internalSecret: string;
|
|
14
|
+
/**
|
|
15
|
+
* The control plane's adapters, where the tenant row that decides lives. Use
|
|
16
|
+
* the raw layer.
|
|
17
|
+
*/
|
|
18
|
+
controlPlaneAdapters: DataAdapters;
|
|
19
|
+
/** Per-push timeout. Defaults to 120s. */
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Builds the `detach` operation's transport for tenants with their own worker
|
|
24
|
+
* and database. The control plane checks that the tenant can be detached from
|
|
25
|
+
* its own tenant row, asks the tenant worker to convert (or remove) the copies
|
|
26
|
+
* in its database, and only when that went through marks its own tenant row
|
|
27
|
+
* detached. A preview or a partial result leaves the row as it was, so a retry
|
|
28
|
+
* can finish the job. Rejects when the push fails or the worker answers non-2xx,
|
|
29
|
+
* which fails the operation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createDispatchDetach(options: DispatchDetachOptions): DetachConfigFn;
|
|
@@ -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;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { TenantMemberRequestStore } from "@authhero/multi-tenancy";
|
|
2
|
+
import type { DispatchNamespace } from "../code-executor";
|
|
3
|
+
export declare const TENANT_MEMBER_REQUESTS_PATH = "/internal/tenant-member-requests";
|
|
4
|
+
export interface DispatchTenantMemberRequestStoreOptions {
|
|
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/*`
|
|
11
|
+
* (`WFP_INTERNAL_SYNC_SECRET`). Sent as a bearer token.
|
|
12
|
+
*/
|
|
13
|
+
internalSecret: string;
|
|
14
|
+
/** Per-call timeout. Defaults to 15s. */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reaches the team-change requests in a tenant worker's own database, for the
|
|
19
|
+
* `tenant-members-request` operation and its reconcile sweep (issue #1550):
|
|
20
|
+
* read a request, answer it, and list the ones still pending. One endpoint,
|
|
21
|
+
* `POST /internal/tenant-member-requests`, takes the action in its body.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDispatchTenantMemberRequestStore(options: DispatchTenantMemberRequestStoreOptions): TenantMemberRequestStore;
|
|
@@ -5,5 +5,8 @@
|
|
|
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";
|
|
9
|
+
export { createDispatchDetach, type DispatchDetachOptions, } from "./dispatch-detach";
|
|
10
|
+
export { createDispatchTenantMemberRequestStore, TENANT_MEMBER_REQUESTS_PATH, type DispatchTenantMemberRequestStoreOptions, } from "./dispatch-tenant-member-requests";
|
|
8
11
|
export { createDispatchRenewSigningCertificates, type DispatchRenewSigningCertificatesOptions, type DispatchRenewSigningCertificatesResult, } from "./dispatch-renew-signing-certificates";
|
|
9
12
|
export { createWfpTenantApp, type WfpTenantEnv, type WfpTenantAppOptions, type WfpSigningCertificateAuthority, } from "./tenant-app";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ExecutionContext } from "hono";
|
|
2
2
|
import { type AuthHeroConfig, type CertificateIssuer, type DataAdapters, type IssuerResolver } from "authhero";
|
|
3
|
-
import { type ControlPlaneDefaultsApplyResult } from "@authhero/multi-tenancy";
|
|
3
|
+
import { type ControlPlaneDefaultsApplyResult, type InheritanceShadowOptions } from "@authhero/multi-tenancy";
|
|
4
4
|
/**
|
|
5
5
|
* Env contract for a WFP tenant worker — the bindings + secrets
|
|
6
6
|
* `createCloudflareWfpD1Provisioner` (and the host's `secrets` resolver) set on
|
|
@@ -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> {
|
|
@@ -84,6 +93,22 @@ export interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
|
|
|
84
93
|
* control plane via `createDispatchRenewSigningCertificates`.
|
|
85
94
|
*/
|
|
86
95
|
signingCertificateAuthority?: (env: Env) => WfpSigningCertificateAuthority | undefined;
|
|
96
|
+
/**
|
|
97
|
+
* Shadow mode for config inheritance, run in this worker: after a request is
|
|
98
|
+
* answered, compare what the projected control-plane rows give this tenant
|
|
99
|
+
* (the runtime fallback) with what its parent chain resolves to, and log the
|
|
100
|
+
* differences as `cfgdiff` in this tenant's own logs
|
|
101
|
+
* (`GET /api/v2/logs?q=type:cfgdiff`, forwarded here). Never changes a
|
|
102
|
+
* response. The comparison runs under `ctx.waitUntil`, so the Worker keeps
|
|
103
|
+
* it alive after the response; it is sampled and rate-limited like the
|
|
104
|
+
* colocated shadow mode. `true` uses the defaults.
|
|
105
|
+
*
|
|
106
|
+
* The worker only knows the control plane's rows (the ones projected into its
|
|
107
|
+
* database) and the tenant's own, so it compares against those. If the
|
|
108
|
+
* tenant's row names a parent this database lacks, the comparison is skipped;
|
|
109
|
+
* a deeper chain is checked by _Verify_ on the control plane.
|
|
110
|
+
*/
|
|
111
|
+
inheritanceShadow?: boolean | InheritanceShadowOptions;
|
|
87
112
|
}
|
|
88
113
|
export interface WfpSigningCertificateAuthority {
|
|
89
114
|
issuer: CertificateIssuer;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type StepConfig, type StepRunner, type DetachConfigFn, type TenantOperationStores } from "@authhero/multi-tenancy";
|
|
2
|
+
import type { TenantOperationWorkflowParams } from "./types";
|
|
3
|
+
export type DetachStepName = "mark-running" | "detach" | "mark-succeeded" | "mark-failed";
|
|
4
|
+
export interface DetachOperationDeps {
|
|
5
|
+
/** Control-plane operation log stores. */
|
|
6
|
+
stores: TenantOperationStores;
|
|
7
|
+
/**
|
|
8
|
+
* How the detach reaches the tenant's database, for example
|
|
9
|
+
* `createDispatchDetach(...)`.
|
|
10
|
+
*/
|
|
11
|
+
detachTenant: DetachConfigFn;
|
|
12
|
+
/**
|
|
13
|
+
* Per-step overrides of the retry/timeout defaults. The `detach` step has no
|
|
14
|
+
* automatic retries unless a host sets them.
|
|
15
|
+
*/
|
|
16
|
+
stepConfig?: Partial<Record<DetachStepName, StepConfig>>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Durable `detach` operation: a synced tenant leaves its parent, as a Workflows
|
|
20
|
+
* instance that writes to the control-plane operation log at every step
|
|
21
|
+
* boundary (the same shape as {@link runSyncDefaultsOperation}, and the same
|
|
22
|
+
* step logic as the inline executor). The operation's `input` (`dry_run`,
|
|
23
|
+
* `clean`) is read from its row, so nothing but ids crosses the Workflows
|
|
24
|
+
* boundary.
|
|
25
|
+
*
|
|
26
|
+
* A dry run records what would change in the operation's `result` and writes
|
|
27
|
+
* nothing. Any failure runs `mark-failed` (event + failed row) and rethrows
|
|
28
|
+
* so the instance ends `errored`; if even that dies, the reconciler copies the
|
|
29
|
+
* engine's terminal state into the row.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runDetachOperation(deps: DetachOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
|
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
* createWfpWorkflowProvisioningHook,
|
|
32
32
|
* reconcileTenantOperations,
|
|
33
33
|
* runProvisionOperation,
|
|
34
|
+
* runSyncDefaultsOperation,
|
|
35
|
+
* runDetachOperation,
|
|
34
36
|
* type ProvisionOperationDeps,
|
|
35
37
|
* type TenantOperationWorkflowParams,
|
|
36
38
|
* } from "@authhero/cloudflare-adapter/workflows";
|
|
@@ -40,7 +42,11 @@
|
|
|
40
42
|
* createWfpTenantProvisioningHook,
|
|
41
43
|
* CloudflareApiClient,
|
|
42
44
|
* } from "@authhero/cloudflare-adapter";
|
|
43
|
-
* import {
|
|
45
|
+
* import {
|
|
46
|
+
* createDispatchSyncConfig,
|
|
47
|
+
* createDispatchDetach,
|
|
48
|
+
* createDispatchSyncDefaults,
|
|
49
|
+
* } from "@authhero/cloudflare-adapter/wfp";
|
|
44
50
|
*
|
|
45
51
|
* // Host-owned: build every workflow dependency from the worker env. Params
|
|
46
52
|
* // carry only { operation_id, tenant_id, kind } — never secrets.
|
|
@@ -69,8 +75,54 @@
|
|
|
69
75
|
*
|
|
70
76
|
* export class TenantOperationWorkflow extends WorkflowEntrypoint<Env, TenantOperationWorkflowParams> {
|
|
71
77
|
* async run(event: WorkflowEvent<TenantOperationWorkflowParams>, step: WorkflowStep) {
|
|
72
|
-
* // CF's WorkflowStep satisfies StepRunner structurally.
|
|
73
|
-
*
|
|
78
|
+
* // CF's WorkflowStep satisfies StepRunner structurally. One class runs
|
|
79
|
+
* // every kind; params carry the kind, never secrets.
|
|
80
|
+
* switch (event.payload.kind) {
|
|
81
|
+
* case "detach": {
|
|
82
|
+
* const adapters = createControlPlaneAdapters(this.env);
|
|
83
|
+
* return runDetachOperation(
|
|
84
|
+
* {
|
|
85
|
+
* stores: {
|
|
86
|
+
* tenantOperations: adapters.tenantOperations!,
|
|
87
|
+
* tenantOperationEvents: adapters.tenantOperationEvents!,
|
|
88
|
+
* },
|
|
89
|
+
* // Checks the tenant row on the control plane, asks the tenant
|
|
90
|
+
* // worker's /internal/detach to convert its rows, then marks the
|
|
91
|
+
* // control-plane row detached.
|
|
92
|
+
* detachTenant: createDispatchDetach({
|
|
93
|
+
* dispatcher: this.env.DISPATCHER,
|
|
94
|
+
* internalSecret: this.env.WFP_INTERNAL_SYNC_SECRET,
|
|
95
|
+
* controlPlaneAdapters: adapters,
|
|
96
|
+
* }),
|
|
97
|
+
* },
|
|
98
|
+
* event.payload,
|
|
99
|
+
* step,
|
|
100
|
+
* );
|
|
101
|
+
* }
|
|
102
|
+
* case "sync-defaults": {
|
|
103
|
+
* const adapters = createControlPlaneAdapters(this.env);
|
|
104
|
+
* return runSyncDefaultsOperation(
|
|
105
|
+
* {
|
|
106
|
+
* stores: {
|
|
107
|
+
* tenantOperations: adapters.tenantOperations!,
|
|
108
|
+
* tenantOperationEvents: adapters.tenantOperationEvents!,
|
|
109
|
+
* },
|
|
110
|
+
* // Reads the chain as stored (ciphertext) and pushes the
|
|
111
|
+
* // resolved block to the tenant worker's /internal/sync-config.
|
|
112
|
+
* syncConfig: createDispatchSyncConfig({
|
|
113
|
+
* dispatcher: this.env.DISPATCHER,
|
|
114
|
+
* internalSecret: this.env.WFP_INTERNAL_SYNC_SECRET,
|
|
115
|
+
* controlPlaneTenantId: this.env.CONTROL_PLANE_TENANT_ID,
|
|
116
|
+
* controlPlaneAdapters: adapters,
|
|
117
|
+
* }),
|
|
118
|
+
* },
|
|
119
|
+
* event.payload,
|
|
120
|
+
* step,
|
|
121
|
+
* );
|
|
122
|
+
* }
|
|
123
|
+
* default:
|
|
124
|
+
* return runProvisionOperation(buildProvisionDeps(this.env), event.payload, step);
|
|
125
|
+
* }
|
|
74
126
|
* }
|
|
75
127
|
* }
|
|
76
128
|
*
|
|
@@ -87,6 +139,14 @@
|
|
|
87
139
|
* binding: env.TENANT_OPERATIONS_WORKFLOW,
|
|
88
140
|
* });
|
|
89
141
|
* console.log("reconcileTenantOperations", result);
|
|
142
|
+
*
|
|
143
|
+
* // Fleet rollouts of synced config (initMultiTenant's `rollouts`):
|
|
144
|
+
* // find tenants whose applied config no longer matches what their
|
|
145
|
+
* // parents publish (the safety net for lost write triggers), then start
|
|
146
|
+
* // the next wave of every open rollout. Pass a Workflows executor as
|
|
147
|
+
* // initMultiTenant's `rolloutExecutor` to run the waves durably.
|
|
148
|
+
* // await reconcileConfigDrift(rollouts);
|
|
149
|
+
* // await advanceRollouts(rollouts);
|
|
90
150
|
* },
|
|
91
151
|
* };
|
|
92
152
|
*
|
|
@@ -13,6 +13,8 @@
|
|
|
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";
|
|
17
|
+
export { runDetachOperation, type DetachOperationDeps, type DetachStepName, } from "./detach-operation";
|
|
16
18
|
export { createProvisionVerifier, TenantProvisionVerificationError, type ProvisionVerifierOptions, } from "./verify";
|
|
17
19
|
export { createCloudflareWorkflowsExecutor, type CloudflareWorkflowsExecutorOptions, } from "./executor";
|
|
18
20
|
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" | "detach";
|
|
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,prune:p})=>{if((await u.tenants.get(t))?.config_sync===`detached`)throw Error(`Tenant "${t}" is detached from its parent: set config_sync back to "inherit-on-read" or "synced" before syncing it`);let m=await(0,e.buildResolvedConfigBlock)(u,t,{legacyControlPlane:l,maxDepth:d}),h=o(a,t),g=new AbortController,_=setTimeout(()=>g.abort(),f),v;try{v=await n.get(h).fetch(`https://tenant.internal${c}`,{method:`POST`,headers:{"content-type":`application/json`,authorization:`Bearer ${s}`},body:JSON.stringify({tenantId:t,resolved:m,dryRun:r,force:i,prune:p}),signal:g.signal})}finally{clearTimeout(_)}if(!v.ok){let e=await v.text().catch(()=>``),t=v.headers.get(`x-authhero-error`);throw Error(`sync-config push to "${h}" failed: ${v.status}${t?` (${t})`:``} ${e.slice(0,256)}`)}return await v.json()}}var u=`/internal/detach`,d=12e4;function f(t){let{dispatcher:n,scriptNameTemplate:i=r,internalSecret:a,controlPlaneAdapters:s,timeoutMs:c=d}=t;return async(t,{dryRun:r,clean:l})=>{let d=await s.tenants.get(t);if(!d)throw Error(`Tenant "${t}" not found`);if(d.config_sync===`detached`)return(0,e.emptyDetachResult)(`already-detached`);(0,e.assertDetachEligible)(t,d);let f=o(i,t),p=new AbortController,m=setTimeout(()=>p.abort(),c),h;try{h=await n.get(f).fetch(`https://tenant.internal${u}`,{method:`POST`,headers:{"content-type":`application/json`,authorization:`Bearer ${a}`},body:JSON.stringify({tenantId:t,dryRun:r,clean:l}),signal:p.signal})}finally{clearTimeout(m)}if(!h.ok){let e=await h.text().catch(()=>``),t=h.headers.get(`x-authhero-error`);throw Error(`detach push to "${f}" failed: ${h.status}${t?` (${t})`:``} ${e.slice(0,256)}`)}let g=await h.json();return!r&&g.status===`detached`&&await(0,e.markTenantDetached)(s,t),g}}var p=`/internal/tenant-member-requests`,m=15e3;function h(e){let{dispatcher:t,scriptNameTemplate:n=r,internalSecret:i,timeoutMs:a=m}=e;async function s(e,r){let s=o(n,e),c=new AbortController,l=setTimeout(()=>c.abort(),a);try{return await t.get(s).fetch(`https://tenant.internal${p}`,{method:`POST`,headers:{"content-type":`application/json`,authorization:`Bearer ${i}`},body:JSON.stringify({tenantId:e,...r}),signal:c.signal})}finally{clearTimeout(l)}}async function c(e,t){let n=await t.text().catch(()=>``),r=t.headers.get(`x-authhero-error`);return Error(`tenant member requests of "${e}" answered ${t.status}${r?` (${r})`:``} ${n.slice(0,256)}`)}return{async get(e,t){let n=await s(e,{action:`get`,id:t});if(n.status===404)return null;if(!n.ok)throw await c(e,n);return await n.json()},async report(e,t,n){let r=await s(e,{action:`report`,id:t,outcome:n});if(!r.ok)throw await c(e,r);return(await r.json()).updated},async listPending(e,{olderThan:t}){let n=await s(e,{action:`list_pending`,olderThan:t});if(!n.ok)throw await c(e,n);return(await n.json()).requests}}}var g=`/internal/renew-signing-certificates`;function _(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${g}`,{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(!v(n))throw Error(`signing-certificate renewal for "${r}" returned an unexpected body`);return{renewed:n.renewed,notDue:n.notDue}}finally{clearTimeout(c)}}}function v(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 y=`cp`,b=`/internal/sync-defaults`,x=`/internal/sync-config`,S=`/internal/detach`,C=`/internal/tenant-member-requests`,w=`/internal/renew-signing-certificates`;function T(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 E(e){let t=`${n.TENANT_URI_PREFIX}${e.tenantId}`;return{issuer:e.issuer,validityDays:e.validityDays,subjectUri:()=>t}}function D(e){return({tenant_id:t})=>t===e.CONTROL_PLANE_TENANT_ID?[e.CONTROL_PLANE_ISSUER]:[]}async function O(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:{[y]: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?y:void 0}),u=(0,e.withRuntimeFallback)(l,{controlPlaneTenantId:a}),d=i.signingCertificateAuthority?.(r),f=d?E(d):void 0,p={dataAdapter:u,...f?{signingCertificateAuthority:f}:{},additionalIssuers:(i.additionalIssuers??D)(r)},m=i.configure?i.configure(p,r):p,h=m.signingCertificateAuthority,{app:g}=(0,n.init)(m),_=new t.Hono;if(_.post(b,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(t,`sync_defaults_apply_failed`,e)}let d;try{d={created:(await(0,n.ensureSigningKey)(l.keys,{name:r.ISSUER,certificateAuthority:h})).created}}catch(e){return T(t,`sync_defaults_signing_key_failed`,e,u)}if(i.onSyncResult)try{await i.onSyncResult(u,r)}catch(e){return T(t,`sync_defaults_on_result_failed`,e,u)}return t.json({...u,signingKey:d})}),_.post(x,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,prune:o.prune===!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(t,`sync_config_apply_failed`,e)}}),_.post(S,async t=>{let n=r.WFP_INTERNAL_SYNC_SECRET,i=t.req.header(`authorization`);if(!n||i!==`Bearer ${n}`)return t.json({error:`unauthorized`},401);let a;try{a=await t.req.json()}catch{return t.json({error:`invalid JSON`},400)}if(!a.tenantId)return t.json({error:`tenantId is required`},400);if(r.TENANT_ID&&a.tenantId!==r.TENANT_ID)return console.error(`[wfp-tenant] detach_tenant_mismatch: detach for "${a.tenantId}" reached the worker of "${r.TENANT_ID}"`),t.header(`X-Authhero-Error`,`detach_tenant_mismatch`),t.json({error:`detach_tenant_mismatch`},403);try{let n=await(0,e.detachTenantRows)(c,a.tenantId,{dryRun:a.dryRun===!0,clean:a.clean===!0});return n.status===`detached`&&await(0,e.markTenantDetached)(c,a.tenantId),t.json(n)}catch(e){return T(t,`detach_failed`,e)}}),_.post(C,async e=>{let t=r.WFP_INTERNAL_SYNC_SECRET,n=e.req.header(`authorization`);if(!t||n!==`Bearer ${t}`)return e.json({error:`unauthorized`},401);let i;try{i=await e.req.json()}catch{return e.json({error:`invalid JSON`},400)}if(!i.tenantId)return e.json({error:`tenantId is required`},400);if(r.TENANT_ID&&i.tenantId!==r.TENANT_ID)return console.error(`[wfp-tenant] member_requests_tenant_mismatch: a request for "${i.tenantId}" reached the worker of "${r.TENANT_ID}"`),e.header(`X-Authhero-Error`,`member_requests_tenant_mismatch`),e.json({error:`member_requests_tenant_mismatch`},403);let a=c.tenantMemberRequests;if(!a)return e.json({error:`tenant member requests are not available`},501);try{switch(i.action){case`get`:{if(!i.id)return e.json({error:`id is required`},400);let t=await a.get(i.tenantId,i.id);return t?e.json(t):e.json({error:`not_found`},404)}case`report`:{let{status:t,result:n,error:r}=i.outcome??{};if(!i.id||t!==`succeeded`&&t!==`failed`)return e.json({error:`id and a final status are required`},400);let o=await a.update(i.tenantId,i.id,{status:t,result:n??null,error:r??null,finished_at:new Date().toISOString()},`pending`);return e.json({updated:o})}case`list_pending`:{let t=await a.list(i.tenantId,{status:`pending`,created_before:i.olderThan,per_page:100});return e.json({requests:t.requests})}default:return e.json({error:`unknown action`},400)}}catch(t){return T(e,`member_requests_failed`,t)}}),_.post(w,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(!h)return e.json({error:`no signing certificate authority`},404);try{let t=await(0,n.renewSigningCertificates)({dataAdapter:{keys:l.keys},certificateAuthority:h});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)):T(e,`signing_certificate_renewal_failed`,t)}}),_.route(`/`,g),i.inheritanceShadow){let t=(0,e.createShadowReporter)(l,i.inheritanceShadow===!0?{}:i.inheritanceShadow),n=r.TENANT_ID||new URL(r.ISSUER).hostname.split(`.`)[0],o,s=async()=>{let e=await l.tenants.get(n);for(let t=0;t<5;t++){let t=e?.parent_tenant_id;if(!t||t===a)return!0;if(e=await l.tenants.get(t),!e)return!1}return!1};k.set(_,e=>{if(!new URL(e.url).pathname.startsWith(`/internal/`)&&n!==a)return(async()=>{if(!await(o??=s()))return;let e=t.schedule(n,a);e&&await e})()})}return _}var k=new WeakMap;function A(e){let t=new WeakMap;return{fetch(n,r,i){let a=t.get(r);return a||(a=O(r,e),t.set(r,a)),a.then(async e=>{let t=await e.fetch(n,r,i),a=k.get(e)?.(n);return a&&i.waitUntil(a),t})}}}exports.TENANT_MEMBER_REQUESTS_PATH=p,exports.createDispatchDetach=f,exports.createDispatchRenewSigningCertificates=_,exports.createDispatchSyncConfig=l,exports.createDispatchSyncDefaults=s,exports.createDispatchTenantMemberRequestStore=h,exports.createWfpTenantApp=A;
|
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, DetachConfigFn, TenantMemberRequestStore, InheritanceShadowOptions } 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,92 @@ 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
|
+
|
|
98
|
+
interface DispatchDetachOptions {
|
|
99
|
+
/** The dispatch namespace binding the tenant workers live in. */
|
|
100
|
+
dispatcher: DispatchNamespace;
|
|
101
|
+
/** Script-name convention. Supports `{tenant_id}`. */
|
|
102
|
+
scriptNameTemplate?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Shared secret the tenant worker checks on `/internal/detach`
|
|
105
|
+
* (`WFP_INTERNAL_SYNC_SECRET`). Sent as a bearer token.
|
|
106
|
+
*/
|
|
107
|
+
internalSecret: string;
|
|
108
|
+
/**
|
|
109
|
+
* The control plane's adapters, where the tenant row that decides lives. Use
|
|
110
|
+
* the raw layer.
|
|
111
|
+
*/
|
|
112
|
+
controlPlaneAdapters: DataAdapters;
|
|
113
|
+
/** Per-push timeout. Defaults to 120s. */
|
|
114
|
+
timeoutMs?: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Builds the `detach` operation's transport for tenants with their own worker
|
|
118
|
+
* and database. The control plane checks that the tenant can be detached from
|
|
119
|
+
* its own tenant row, asks the tenant worker to convert (or remove) the copies
|
|
120
|
+
* in its database, and only when that went through marks its own tenant row
|
|
121
|
+
* detached. A preview or a partial result leaves the row as it was, so a retry
|
|
122
|
+
* can finish the job. Rejects when the push fails or the worker answers non-2xx,
|
|
123
|
+
* which fails the operation.
|
|
124
|
+
*/
|
|
125
|
+
declare function createDispatchDetach(options: DispatchDetachOptions): DetachConfigFn;
|
|
126
|
+
|
|
127
|
+
declare const TENANT_MEMBER_REQUESTS_PATH = "/internal/tenant-member-requests";
|
|
128
|
+
interface DispatchTenantMemberRequestStoreOptions {
|
|
129
|
+
/** The dispatch namespace binding the tenant workers live in. */
|
|
130
|
+
dispatcher: DispatchNamespace;
|
|
131
|
+
/** Script-name convention. Supports `{tenant_id}`. */
|
|
132
|
+
scriptNameTemplate?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Shared secret the tenant worker checks on `/internal/*`
|
|
135
|
+
* (`WFP_INTERNAL_SYNC_SECRET`). Sent as a bearer token.
|
|
136
|
+
*/
|
|
137
|
+
internalSecret: string;
|
|
138
|
+
/** Per-call timeout. Defaults to 15s. */
|
|
139
|
+
timeoutMs?: number;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reaches the team-change requests in a tenant worker's own database, for the
|
|
143
|
+
* `tenant-members-request` operation and its reconcile sweep (issue #1550):
|
|
144
|
+
* read a request, answer it, and list the ones still pending. One endpoint,
|
|
145
|
+
* `POST /internal/tenant-member-requests`, takes the action in its body.
|
|
146
|
+
*/
|
|
147
|
+
declare function createDispatchTenantMemberRequestStore(options: DispatchTenantMemberRequestStoreOptions): TenantMemberRequestStore;
|
|
148
|
+
|
|
63
149
|
interface DispatchRenewSigningCertificatesOptions {
|
|
64
150
|
/** The dispatch namespace binding the tenant workers live in. */
|
|
65
151
|
dispatcher: DispatchNamespace;
|
|
@@ -117,6 +203,15 @@ interface WfpTenantEnv {
|
|
|
117
203
|
CONTROL_PLANE_ENCRYPTION_KEY?: string;
|
|
118
204
|
/** Shared secret authenticating control-plane pushes to `/internal/sync-defaults`. */
|
|
119
205
|
WFP_INTERNAL_SYNC_SECRET?: string;
|
|
206
|
+
/**
|
|
207
|
+
* The tenant this worker serves, set when the script is provisioned (plain
|
|
208
|
+
* text binding or secret, next to `ISSUER`). When present,
|
|
209
|
+
* `/internal/sync-config` refuses a push addressed to any other tenant, so a
|
|
210
|
+
* misrouted push can never write another tenant's config into this database.
|
|
211
|
+
* Optional so workers provisioned before it existed keep working; set it from
|
|
212
|
+
* the provisioner's `secrets` resolver.
|
|
213
|
+
*/
|
|
214
|
+
TENANT_ID?: string;
|
|
120
215
|
[key: string]: unknown;
|
|
121
216
|
}
|
|
122
217
|
interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
|
|
@@ -170,6 +265,22 @@ interface WfpTenantAppOptions<Env extends WfpTenantEnv = WfpTenantEnv> {
|
|
|
170
265
|
* control plane via `createDispatchRenewSigningCertificates`.
|
|
171
266
|
*/
|
|
172
267
|
signingCertificateAuthority?: (env: Env) => WfpSigningCertificateAuthority | undefined;
|
|
268
|
+
/**
|
|
269
|
+
* Shadow mode for config inheritance, run in this worker: after a request is
|
|
270
|
+
* answered, compare what the projected control-plane rows give this tenant
|
|
271
|
+
* (the runtime fallback) with what its parent chain resolves to, and log the
|
|
272
|
+
* differences as `cfgdiff` in this tenant's own logs
|
|
273
|
+
* (`GET /api/v2/logs?q=type:cfgdiff`, forwarded here). Never changes a
|
|
274
|
+
* response. The comparison runs under `ctx.waitUntil`, so the Worker keeps
|
|
275
|
+
* it alive after the response; it is sampled and rate-limited like the
|
|
276
|
+
* colocated shadow mode. `true` uses the defaults.
|
|
277
|
+
*
|
|
278
|
+
* The worker only knows the control plane's rows (the ones projected into its
|
|
279
|
+
* database) and the tenant's own, so it compares against those. If the
|
|
280
|
+
* tenant's row names a parent this database lacks, the comparison is skipped;
|
|
281
|
+
* a deeper chain is checked by _Verify_ on the control plane.
|
|
282
|
+
*/
|
|
283
|
+
inheritanceShadow?: boolean | InheritanceShadowOptions;
|
|
173
284
|
}
|
|
174
285
|
interface WfpSigningCertificateAuthority {
|
|
175
286
|
issuer: CertificateIssuer;
|
|
@@ -193,5 +304,5 @@ declare function createWfpTenantApp<Env extends WfpTenantEnv = WfpTenantEnv>(opt
|
|
|
193
304
|
fetch: (request: Request, env: Env, ctx: ExecutionContext) => Promise<Response>;
|
|
194
305
|
};
|
|
195
306
|
|
|
196
|
-
export { createDispatchRenewSigningCertificates, createDispatchSyncDefaults, createWfpTenantApp };
|
|
197
|
-
export type { DispatchRenewSigningCertificatesOptions, DispatchRenewSigningCertificatesResult, DispatchSyncDefaultsOptions, WfpSigningCertificateAuthority, WfpTenantAppOptions, WfpTenantEnv };
|
|
307
|
+
export { TENANT_MEMBER_REQUESTS_PATH, createDispatchDetach, createDispatchRenewSigningCertificates, createDispatchSyncConfig, createDispatchSyncDefaults, createDispatchTenantMemberRequestStore, createWfpTenantApp };
|
|
308
|
+
export type { DispatchDetachOptions, DispatchRenewSigningCertificatesOptions, DispatchRenewSigningCertificatesResult, DispatchSyncConfigOptions, DispatchSyncDefaultsOptions, DispatchTenantMemberRequestStoreOptions, WfpSigningCertificateAuthority, WfpTenantAppOptions, WfpTenantEnv };
|