@lunora/runtime 1.0.0-alpha.27 → 1.0.0-alpha.28

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/index.d.mts CHANGED
@@ -425,13 +425,6 @@ interface AuthAdmin {
425
425
  userId: string;
426
426
  }) => Promise<AuthUser>;
427
427
  }
428
- /**
429
- * Read-only subset of {@link AuthAdmin}, kept as an alias for the former
430
- * `authIntrospector` option (which the worker still honours as a browse-only
431
- * fallback). Prefer wiring `authAdmin` with `@lunora/auth`'s `createAuthAdmin`
432
- * so the mutation endpoints light up too.
433
- */
434
- type AuthIntrospector = Pick<AuthAdmin, "listSessions" | "listUsers">;
435
428
  /** Closure-scoped worker helpers the auth routes borrow (so this module stays out of the worker's god-closure). */
436
429
  /**
437
430
  * A compact, transport-safe description of one function argument — the runtime
@@ -2021,12 +2014,6 @@ interface WorkerOptions {
2021
2014
  */
2022
2015
  authHandler?: (request: Request) => Promise<Response | undefined>;
2023
2016
  /**
2024
- * @deprecated Use {@link WorkerOptions.authAdmin} (an {@link AuthAdmin}),
2025
- * which also lights up the user-management mutation endpoints. Still honored
2026
- * as a read-only fallback for the browse endpoints.
2027
- */
2028
- authIntrospector?: AuthIntrospector;
2029
- /**
2030
2017
  * Optional table-level authorization callback for fan-out RPC envelopes.
2031
2018
  * Called after `resolveIdentity` and before `coordinator.fanOut` walks
2032
2019
  * the registry. Returning `false` rejects the request with 403
@@ -2250,6 +2237,20 @@ interface WorkerOptions {
2250
2237
  */
2251
2238
  queue?: QueueConsumerHandler;
2252
2239
  /**
2240
+ * Enforce the ephemeral WS admin token: when `true`,
2241
+ * the worker's WS admin gate rejects the raw master admin token in the
2242
+ * `?token=` query parameter — only a short-lived sub-token minted by
2243
+ * `POST /_lunora/admin/ws-token` (or the master token in the
2244
+ * `Authorization` HEADER, which never leaks via URLs) authorizes. Off by
2245
+ * default (the master token in `?token=` keeps working); also settable per
2246
+ * deployment via `env.LUNORA_REQUIRE_EPHEMERAL_WS_TOKEN`
2247
+ * (`1`/`true`/`on`/`yes`/`enabled`), which the shard/relay Durable Objects
2248
+ * honor for their own upgrade gate too. Flipping it on is the step that
2249
+ * actually closes the URL/log leak — do so once every studio the
2250
+ * deployment uses mints ephemeral tokens.
2251
+ */
2252
+ requireEphemeralWsToken?: boolean;
2253
+ /**
2253
2254
  * Resolve the calling identity from the inbound RPC request. Called once
2254
2255
  * per RPC (and per fan-out) before the request is forwarded to the
2255
2256
  * shard. The returned `userId` becomes `ctx.auth.userId` on the shard
@@ -2862,4 +2863,4 @@ declare const otlpSink: (options: OtlpSinkOptions) => ObservabilitySink;
2862
2863
  */
2863
2864
  declare const combineSinks: (...sinks: ObservabilitySink[]) => ObservabilitySink;
2864
2865
  declare const VERSION: string;
2865
- export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthIntrospector, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
2866
+ export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
package/dist/index.d.ts CHANGED
@@ -425,13 +425,6 @@ interface AuthAdmin {
425
425
  userId: string;
426
426
  }) => Promise<AuthUser>;
427
427
  }
428
- /**
429
- * Read-only subset of {@link AuthAdmin}, kept as an alias for the former
430
- * `authIntrospector` option (which the worker still honours as a browse-only
431
- * fallback). Prefer wiring `authAdmin` with `@lunora/auth`'s `createAuthAdmin`
432
- * so the mutation endpoints light up too.
433
- */
434
- type AuthIntrospector = Pick<AuthAdmin, "listSessions" | "listUsers">;
435
428
  /** Closure-scoped worker helpers the auth routes borrow (so this module stays out of the worker's god-closure). */
436
429
  /**
437
430
  * A compact, transport-safe description of one function argument — the runtime
@@ -2021,12 +2014,6 @@ interface WorkerOptions {
2021
2014
  */
2022
2015
  authHandler?: (request: Request) => Promise<Response | undefined>;
2023
2016
  /**
2024
- * @deprecated Use {@link WorkerOptions.authAdmin} (an {@link AuthAdmin}),
2025
- * which also lights up the user-management mutation endpoints. Still honored
2026
- * as a read-only fallback for the browse endpoints.
2027
- */
2028
- authIntrospector?: AuthIntrospector;
2029
- /**
2030
2017
  * Optional table-level authorization callback for fan-out RPC envelopes.
2031
2018
  * Called after `resolveIdentity` and before `coordinator.fanOut` walks
2032
2019
  * the registry. Returning `false` rejects the request with 403
@@ -2250,6 +2237,20 @@ interface WorkerOptions {
2250
2237
  */
2251
2238
  queue?: QueueConsumerHandler;
2252
2239
  /**
2240
+ * Enforce the ephemeral WS admin token: when `true`,
2241
+ * the worker's WS admin gate rejects the raw master admin token in the
2242
+ * `?token=` query parameter — only a short-lived sub-token minted by
2243
+ * `POST /_lunora/admin/ws-token` (or the master token in the
2244
+ * `Authorization` HEADER, which never leaks via URLs) authorizes. Off by
2245
+ * default (the master token in `?token=` keeps working); also settable per
2246
+ * deployment via `env.LUNORA_REQUIRE_EPHEMERAL_WS_TOKEN`
2247
+ * (`1`/`true`/`on`/`yes`/`enabled`), which the shard/relay Durable Objects
2248
+ * honor for their own upgrade gate too. Flipping it on is the step that
2249
+ * actually closes the URL/log leak — do so once every studio the
2250
+ * deployment uses mints ephemeral tokens.
2251
+ */
2252
+ requireEphemeralWsToken?: boolean;
2253
+ /**
2253
2254
  * Resolve the calling identity from the inbound RPC request. Called once
2254
2255
  * per RPC (and per fan-out) before the request is forwarded to the
2255
2256
  * shard. The returned `userId` becomes `ctx.auth.userId` on the shard
@@ -2862,4 +2863,4 @@ declare const otlpSink: (options: OtlpSinkOptions) => ObservabilitySink;
2862
2863
  */
2863
2864
  declare const combineSinks: (...sinks: ObservabilitySink[]) => ObservabilitySink;
2864
2865
  declare const VERSION: string;
2865
- export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthIntrospector, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
2866
+ export { type AdminTableResolver, type AirbyteMessage, type AnalyticsEngineDataPointLike, type AnalyticsEngineDatasetLike, type AnalyticsEngineSinkOptions, type AuthAdmin, type AuthCapabilities, type AuthConfigInfo, type AuthImpersonation, type AuthPage, type AuthSession, type AuthUser, type AuthUserFieldSpec, type BackupManifest, type BackupStore, type ComposeIdentityResolversErrorMode, type ComposeIdentityResolversOptions, type ConnectorChange, type ConnectorSyncPage, type CorsOptions, type CronHandler, type CronJobDispatch, type CronJobInfo, type CrossShardCounter, type CrossShardReader, type CrossShardRelationCapabilities, type CrossShardRelationOptions, type CsrfOptions, DEFAULT_REGISTRY_CACHE_TTL_MS, type DurableObjectJurisdiction, type DynamicShardRegistry, type DynamicShardRegistryOptions, type ExecutionContextLike, type ExportFanOutRequest, type ExportFanOutResult, type FanOutRequest, type FanOutResult, type FanOutSpec, type FivetranResponse, type FrameworkHostHandler, type FrameworkWorkerOptions, type FrameworkWorkerOptionsInput, type FunctionDescriptor, type FunctionRegistryEntry, type FunctionRegistryLike, type GlobalExportFunction as GlobalExportFn, type GlobalImportFunction as GlobalImportFn, type GlobalIntrospector, type GlobalTableInfo as GlobalTableInfoMeta, type GlobalTablePage as GlobalTablePageMeta, type HttpActionContext, type HttpActionLike, type HttpRouterLike, type IdentityContractLike, type IdentityResolver, type IdentityValidation, type ImportFanOutRequest, type ImportFanOutResult, type KvIntrospector, type KvKeyEntry, type KvKeyListResult, type KvNamespaceSummary, type KvValueResult, type ListAuthUsersOptions, type LogEvent, type LogLevel, LunoraError, type LunoraErrorBody, type LunoraHandlerOptions, type LunoraWorker, type MergeStrategy, type MigrationFanOutRequest, type MigrationFanOutResult, NOOP_EXECUTION_CONTEXT, type ObservabilityEvent, type ObservabilitySink, type ObservabilitySinkContext, type OtlpSinkOptions, type QueryCoordinator, type QueryCoordinatorOptions, type RankFanOutRequest, type RankFanOutResult, type RankPageFanOutRequest, type RankPageFanOutResult, type ResolvedSecurity, type ResolvedShard, type Route, type RpcContext, type RpcEnvelope, SHARD_REGISTRY_DO_NAME, type ScheduledControllerLike, type SecurityHeadersOptions, type SecurityOptions, type SentrySinkOptions, type ShardError, type ShardExportOutcome, type ShardImportOutcome, type ShardMigrationOutcome, type ShardNamespaceLike, type ShardRankOutcome, type ShardRankPageOutcome, type ShardRegistry, type ShardTrafficEntry, type ShardTrafficFanOutRequest, type ShardTrafficFanOutResult, type ShardingInfo, type StorageListFunction as StorageListFn, type StorageObject, VERSION, type VectorIndexSummary, type VectorIntrospector, type VectorQueryMatch, type WebhookSinkOptions, type WorkerOptions, analyticsEngineSink, applyJurisdiction, combineSinks, composeIdentityResolvers, composeWorker, consoleSink, createCrossShardRelationCapabilities, createDynamicShardRegistry, createLunoraHandler, createQueryCoordinator, createStaticShardRegistry, createWorker, decorateResponse, defineRpcEnvelope, emitLogEvent, emitRpcEvent, enforceOrigin, handleCorsPreflight, mergeStrategyForAggregate, otlpSink, resolveLunoraOptions, resolveSecurity, resolveShard, routeIdentityResolvers, sentrySink, toAirbyteMessages, toErrorResponse, toFivetranResponse, webhookSink, withFrameworkWorker };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  export { toAirbyteMessages, toFivetranResponse } from './packem_shared/toAirbyteMessages-DrHdplb4.mjs';
2
- export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-UZKPkF3Q.mjs';
2
+ export { composeWorker, createLunoraHandler, createWorker, defineRpcEnvelope, resolveLunoraOptions, withFrameworkWorker } from './packem_shared/composeWorker-B6Pzfwhg.mjs';
3
3
  export { createCrossShardRelationCapabilities } from './packem_shared/createCrossShardRelationCapabilities-CbcWjkAn.mjs';
4
4
  export { DEFAULT_REGISTRY_CACHE_TTL_MS, SHARD_REGISTRY_DO_NAME, createDynamicShardRegistry } from './packem_shared/DEFAULT_REGISTRY_CACHE_TTL_MS-B3pA7aXp.mjs';
5
5
  export { LunoraError, toErrorResponse } from './packem_shared/LunoraError-Bpb9EFJ3.mjs';
@@ -21,6 +21,75 @@ const evictOldestEntry = (map, capacity) => {
21
21
  const RELAY_NAME_INFIX = "::relay::";
22
22
  const relayName = (ownerKey, index) => `${ownerKey}${RELAY_NAME_INFIX}${String(index)}`;
23
23
 
24
+ const textEncoder = new TextEncoder();
25
+ const toBase64Url = (bytes) => {
26
+ const binary = String.fromCodePoint(...bytes);
27
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
28
+ };
29
+ const fromBase64Url = (input) => {
30
+ const padded = input.replaceAll("-", "+").replaceAll("_", "/") + "===".slice((input.length + 3) % 4);
31
+ const binary = atob(padded);
32
+ const bytes = new Uint8Array(binary.length);
33
+ for (let index = 0; index < binary.length; index += 1) {
34
+ bytes[index] = binary.codePointAt(index) ?? 0;
35
+ }
36
+ return bytes;
37
+ };
38
+ const KEY_CACHE_MAX = 64;
39
+ const keyCache = /* @__PURE__ */ new Map();
40
+ const importHmacKey = async (secret) => {
41
+ const cached = keyCache.get(secret);
42
+ if (cached) {
43
+ return cached;
44
+ }
45
+ evictOldestEntry(keyCache, KEY_CACHE_MAX);
46
+ const keyPromise = crypto.subtle.importKey("raw", textEncoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]);
47
+ keyCache.set(secret, keyPromise);
48
+ return keyPromise;
49
+ };
50
+ const signCanonical = async (secret, canonical) => {
51
+ const cryptoKey = await importHmacKey(secret);
52
+ const signature = await crypto.subtle.sign("HMAC", cryptoKey, textEncoder.encode(canonical));
53
+ return toBase64Url(new Uint8Array(signature));
54
+ };
55
+ const verifyCanonical = async (secret, canonical, sigBytes) => {
56
+ const cryptoKey = await importHmacKey(secret);
57
+ return crypto.subtle.verify("HMAC", cryptoKey, sigBytes, textEncoder.encode(canonical));
58
+ };
59
+
60
+ const WS_ADMIN_TOKEN_VERSION = "v1";
61
+ const WS_ADMIN_TOKEN_TTL_MS = 6e4;
62
+ const mintWsAdminToken = async (secret, options = {}) => {
63
+ const expiresAtMs = (options.now ?? Date.now()) + (options.ttlMs ?? WS_ADMIN_TOKEN_TTL_MS);
64
+ const canonical = `${WS_ADMIN_TOKEN_VERSION}.${String(expiresAtMs)}`;
65
+ const signature = await signCanonical(secret, canonical);
66
+ return { expiresAtMs, token: `${canonical}.${signature}` };
67
+ };
68
+ const verifyWsAdminToken = async (secret, token, now = Date.now()) => {
69
+ if (secret.length === 0 || token.length === 0) {
70
+ return false;
71
+ }
72
+ const parts = token.split(".");
73
+ if (parts.length !== 3) {
74
+ return false;
75
+ }
76
+ const [version, expString, signature] = parts;
77
+ if (version !== WS_ADMIN_TOKEN_VERSION || signature.length === 0) {
78
+ return false;
79
+ }
80
+ const expiresAtMs = Number(expString);
81
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= now) {
82
+ return false;
83
+ }
84
+ let signatureBytes;
85
+ try {
86
+ signatureBytes = fromBase64Url(signature);
87
+ } catch {
88
+ return false;
89
+ }
90
+ return verifyCanonical(secret, `${version}.${expString}`, signatureBytes);
91
+ };
92
+
24
93
  const AUTH_BASE = "/_lunora/admin/auth";
25
94
  const AUTH_ADMIN_ERROR_STATUS = {
26
95
  INVITER_REQUIRED: 400,
@@ -1615,11 +1684,11 @@ const buildScheduledAdminRoutes = (deps) => {
1615
1684
  const stub = resolveSchedulerStub(request);
1616
1685
  return stub.fetch(new Request("https://scheduler.internal/status", { method: "GET" }));
1617
1686
  };
1618
- const handleScheduledWebSocket = (request) => {
1687
+ const handleScheduledWebSocket = async (request) => {
1619
1688
  if (request.headers.get("Upgrade") !== "websocket") {
1620
1689
  throw new LunoraError("WebSocket upgrade header missing", { code: "BAD_REQUEST", status: 426 });
1621
1690
  }
1622
- if (!checkWsAdmin(request)) {
1691
+ if (!await checkWsAdmin(request)) {
1623
1692
  throw new LunoraError("admin authorization required", { code: "ADMIN_FORBIDDEN", status: 403 });
1624
1693
  }
1625
1694
  const namespace = requireSchedulerNamespace();
@@ -1920,10 +1989,12 @@ const WS_PATH = "/_lunora/ws";
1920
1989
  const VOICE_PATH_PREFIX = "/_lunora/voice/";
1921
1990
  const SCHEDULER_DISPATCH_PATH = "/_lunora/scheduler/dispatch";
1922
1991
  const CRON_JOBS_RUN_PATH = "/_lunora/admin/cron-jobs/run";
1992
+ const ADMIN_WS_TOKEN_PATH = "/_lunora/admin/ws-token";
1923
1993
  const ADMIN_PATH_PREFIX = "/_lunora/admin/";
1924
1994
  const MIGRATE_PATH = "/_lunora/migrate";
1925
1995
  const STATUS_PATH = "/_lunora/status";
1926
1996
  const isAdminPath = (pathname) => pathname.startsWith(ADMIN_PATH_PREFIX) || pathname === MIGRATE_PATH;
1997
+ const REQUIRE_EPHEMERAL_ENV_VALUES = /* @__PURE__ */ new Set(["1", "enabled", "on", "true", "yes"]);
1927
1998
  const readForwardedIdentity = (request) => {
1928
1999
  const forwardedUserId = request.headers.get("x-lunora-userid");
1929
2000
  const forwardedIdentity = request.headers.get("x-lunora-identity");
@@ -2188,12 +2259,21 @@ const checkAdminAuth = (request, expected) => {
2188
2259
  }
2189
2260
  return constantTimeEqual(expected, rest.join(" ").trim());
2190
2261
  };
2191
- const checkAdminWsToken = (request, expected) => {
2262
+ const checkAdminWsToken = async (request, expected, requireEphemeral) => {
2192
2263
  if (!expected || expected.length === 0) {
2193
2264
  return false;
2194
2265
  }
2195
2266
  const supplied = new URL(request.url).searchParams.get("token");
2196
- return supplied !== null && constantTimeEqual(expected, supplied);
2267
+ if (supplied === null) {
2268
+ return false;
2269
+ }
2270
+ if (await verifyWsAdminToken(expected, supplied)) {
2271
+ return true;
2272
+ }
2273
+ if (requireEphemeral) {
2274
+ return false;
2275
+ }
2276
+ return constantTimeEqual(expected, supplied);
2197
2277
  };
2198
2278
  const createWorker = (options) => {
2199
2279
  const defaultShard = options.defaultShardKey ?? "__root__";
@@ -2202,11 +2282,20 @@ const createWorker = (options) => {
2202
2282
  const schedulerDO = options.schedulerDO === void 0 ? void 0 : applyJurisdiction(options.schedulerDO, options.jurisdiction);
2203
2283
  let envAdminToken;
2204
2284
  const effectiveAdminToken = () => options.adminToken ?? envAdminToken;
2285
+ let envRequireEphemeralWsToken;
2286
+ const effectiveRequireEphemeralWsToken = () => options.requireEphemeralWsToken ?? envRequireEphemeralWsToken ?? false;
2205
2287
  const resolveAdminTokenFromEnv = (env) => {
2288
+ const record = env ?? {};
2289
+ if (envRequireEphemeralWsToken === void 0 && options.requireEphemeralWsToken === void 0) {
2290
+ const raw = record["LUNORA_REQUIRE_EPHEMERAL_WS_TOKEN"];
2291
+ if (typeof raw === "string" && raw.length > 0) {
2292
+ envRequireEphemeralWsToken = REQUIRE_EPHEMERAL_ENV_VALUES.has(raw.trim().toLowerCase());
2293
+ }
2294
+ }
2206
2295
  if (envAdminToken !== void 0 || options.adminToken !== void 0) {
2207
2296
  return;
2208
2297
  }
2209
- const value = (env ?? {})["LUNORA_ADMIN_TOKEN"];
2298
+ const value = record["LUNORA_ADMIN_TOKEN"];
2210
2299
  if (typeof value === "string" && value.length > 0) {
2211
2300
  envAdminToken = value;
2212
2301
  }
@@ -2447,7 +2536,7 @@ const createWorker = (options) => {
2447
2536
  return resolveShard(requireSchedulerNamespace(), options.schedulerInstanceName ?? "default");
2448
2537
  };
2449
2538
  const scheduledAdminRoutes = buildScheduledAdminRoutes({
2450
- checkWsAdmin: (request) => requestIsAdmin(request) || checkAdminWsToken(request, effectiveAdminToken()),
2539
+ checkWsAdmin: async (request) => requestIsAdmin(request) || checkAdminWsToken(request, effectiveAdminToken(), effectiveRequireEphemeralWsToken()),
2451
2540
  requireSchedulerNamespace,
2452
2541
  resolveSchedulerStub,
2453
2542
  schedulerInstanceName: options.schedulerInstanceName ?? "default"
@@ -2988,33 +3077,22 @@ const createWorker = (options) => {
2988
3077
  const tables = options.backupTables;
2989
3078
  let rows = 0;
2990
3079
  let bytes = 0;
2991
- let streamError;
2992
- const stream = new ReadableStream({
2993
- async pull(streamController) {
2994
- const writeRow = (row) => {
2995
- const encoded = NDJSON_ENCODER.encode(`${JSON.stringify(row)}
2996
- `);
2997
- rows += 1;
2998
- bytes += encoded.byteLength;
2999
- streamController.enqueue(encoded);
3000
- };
3001
- try {
3002
- await streamExportRows(options, coordinator, forwardedHeaders, tables, writeRow, shardDO);
3003
- streamController.close();
3004
- } catch (error) {
3005
- streamError = error instanceof Error ? error : new Error(String(error));
3006
- streamController.error(error);
3007
- }
3008
- }
3009
- });
3080
+ const parts = [];
3081
+ const writeRow = (row) => {
3082
+ const line = `${JSON.stringify(row)}
3083
+ `;
3084
+ rows += 1;
3085
+ bytes += NDJSON_ENCODER.encode(line).byteLength;
3086
+ parts.push(line);
3087
+ };
3088
+ await streamExportRows(options, coordinator, forwardedHeaders, tables, writeRow, shardDO);
3010
3089
  const prefix = options.backupPrefix ?? "backups/";
3011
3090
  const timestamp = new Date(controller.scheduledTime).toISOString();
3012
3091
  const fileKey = `${prefix}lunora-backup-${timestamp.replaceAll(/[.:]/gu, "-")}.ndjson`;
3013
3092
  const manifestKey = `${fileKey}.manifest.json`;
3014
- await store.put(fileKey, stream, { httpMetadata: { contentType: "application/x-ndjson" } });
3015
- if (streamError !== void 0) {
3016
- throw streamError;
3017
- }
3093
+ await store.put(fileKey, new Blob(parts, { type: "application/x-ndjson" }), {
3094
+ httpMetadata: { contentType: "application/x-ndjson" }
3095
+ });
3018
3096
  const manifest = {
3019
3097
  bytes,
3020
3098
  createdAt: timestamp,
@@ -3100,6 +3178,24 @@ const createWorker = (options) => {
3100
3178
  [RPC_BATCH_PATH]: (request, env, _url, context) => handleBatchRpc(request, env, context),
3101
3179
  [SCHEDULER_DISPATCH_PATH]: (request, env) => handleSchedulerDispatch(request, env),
3102
3180
  [CRON_JOBS_RUN_PATH]: (request, env) => handleRunCronJob(request, env),
3181
+ // Mint a short-lived HMAC-signed WS admin sub-token. Gated by the master
3182
+ // admin bearer (header) / `adminGate`; the studio then sends the minted
3183
+ // token — not the master credential — in the WS `?token=`
3184
+ // query string. Signed with the master token itself, so both isolates
3185
+ // verify statelessly and rotating `LUNORA_ADMIN_TOKEN` invalidates every
3186
+ // outstanding sub-token. `no-store` keeps the token out of caches.
3187
+ [ADMIN_WS_TOKEN_PATH]: async (request) => {
3188
+ if (request.method !== "POST") {
3189
+ throw new LunoraError("ws-token endpoint requires POST", { code: "METHOD_NOT_ALLOWED", status: 405 });
3190
+ }
3191
+ assertAdminAuthorized(request);
3192
+ const signingSecret = effectiveAdminToken();
3193
+ if (signingSecret === void 0) {
3194
+ throw new LunoraError("ws-token minting requires a configured admin token", { code: "ADMIN_TOKEN_NOT_CONFIGURED", status: 400 });
3195
+ }
3196
+ const minted = await mintWsAdminToken(signingSecret);
3197
+ return Response.json(minted, { headers: { "cache-control": "no-store" } });
3198
+ },
3103
3199
  // Extracted handler clusters built above, merged in (mirroring the auth
3104
3200
  // plane below): orchestration (migrate / rank / rankpage / shard-traffic /
3105
3201
  // pitr), data-movement (export / import / sync / connector-sync / apply),
@@ -3117,8 +3213,7 @@ const createWorker = (options) => {
3117
3213
  // `AuthAdmin` op, dispatched by the descriptor table in `./auth-admin-routes`.
3118
3214
  ...buildAuthAdminRoutes({
3119
3215
  assertAdmin: assertAdminAuthorized,
3120
- // eslint-disable-next-line sonarjs/deprecation -- `authIntrospector` is the intentional read-only fallback
3121
- getAuthAdmin: () => options.authAdmin ?? options.authIntrospector,
3216
+ getAuthAdmin: () => options.authAdmin,
3122
3217
  parsePaging,
3123
3218
  queryParameter,
3124
3219
  readJsonBody: readJsonBodyWithLimit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/runtime",
3
- "version": "1.0.0-alpha.27",
3
+ "version": "1.0.0-alpha.28",
4
4
  "description": "Lunora Worker runtime: the RPC router, shard resolver, and query coordinator",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/errors": "1.0.0-alpha.4"
49
+ "@lunora/errors": "1.0.0-alpha.5"
50
50
  },
51
51
  "engines": {
52
52
  "node": "^22.15.0 || >=24.11.0"