From 40e2e31fd7568de4f094d58f8a970f448469a59e Mon Sep 17 00:00:00 2001 From: Matt Provost Date: Wed, 25 Mar 2026 16:19:06 +0000 Subject: [PATCH 1/5] feat: add AccessContext types Signed-off-by: Matt Provost --- src/workerd/api/global-scope.h | 4 + types/defines/access.d.ts | 23 +++ .../experimental/index.d.ts | 156 ++++++++++++++++++ .../generated-snapshot/experimental/index.ts | 156 ++++++++++++++++++ types/generated-snapshot/latest/index.d.ts | 156 ++++++++++++++++++ types/generated-snapshot/latest/index.ts | 156 ++++++++++++++++++ 6 files changed, 651 insertions(+) create mode 100644 types/defines/access.d.ts diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index 3befcebbe8e..fec2afd013f 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -340,11 +340,13 @@ class ExecutionContext: public jsg::Object { readonly key?: string; readonly override?: string; }; + readonly access?: AccessContext; }); } else { JSG_TS_OVERRIDE( { readonly props: Props; readonly exports: Cloudflare.Exports; + readonly access?: AccessContext; }); } } else { @@ -357,10 +359,12 @@ class ExecutionContext: public jsg::Object { readonly key?: string; readonly override?: string; }; + readonly access?: AccessContext; }); } else { JSG_TS_OVERRIDE( { readonly props: Props; + readonly access?: AccessContext; }); } } diff --git a/types/defines/access.d.ts b/types/defines/access.d.ts new file mode 100644 index 00000000000..818d9541baa --- /dev/null +++ b/types/defines/access.d.ts @@ -0,0 +1,23 @@ +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + */ +type Identity = object; + +/** + * Cloudflare Access authentication information for the current request. + */ +interface AccessContext { + /** + * The audience claim from the Access JWT. This identifies which Access + * application the request matched. + */ + readonly aud: string; + + /** + * Fetches the full identity information for the authenticated user. + * + * @returns The subject's identity, if one exists + */ + getIdentity(): Promise; +} diff --git a/types/generated-snapshot/experimental/index.d.ts b/types/generated-snapshot/experimental/index.d.ts index 08e93fe3474..498106cb5d9 100755 --- a/types/generated-snapshot/experimental/index.d.ts +++ b/types/generated-snapshot/experimental/index.d.ts @@ -503,6 +503,7 @@ interface ExecutionContext { }; tracing?: Tracing; abort(reason?: any): void; + readonly access?: AccessContext; } type ExportedHandlerFetchHandler< Env = unknown, @@ -4734,6 +4735,27 @@ declare abstract class Span { get isTraced(): boolean; setAttribute(key: string, value?: boolean | number | string): void; } +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + */ +type Identity = object; +/** + * Cloudflare Access authentication information for the current request. + */ +interface AccessContext { + /** + * The audience claim from the Access JWT. This identifies which Access + * application the request matched. + */ + readonly aud: string; + /** + * Fetches the full identity information for the authenticated user. + * + * @returns The subject's identity, if one exists + */ + getIdentity(): Promise; +} // ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error {} interface AiSearchNotFoundError extends Error {} @@ -13350,6 +13372,140 @@ declare module "cloudflare:email" { }; export { _EmailMessage as EmailMessage }; } +/** + * Evaluation context for targeting rules. + * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. + */ +type EvaluationContext = Record; +interface EvaluationDetails { + flagKey: string; + value: T; + variant?: string | undefined; + reason?: string | undefined; + errorCode?: string | undefined; + errorMessage?: string | undefined; +} +interface FlagEvaluationError extends Error {} +/** + * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. + * + * @example + * ```typescript + * // Get a boolean flag value with a default + * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); + * + * // Get a flag value with evaluation context for targeting + * const variant = await env.FLAGS.getStringValue('experiment', 'control', { + * userId: 'user-123', + * country: 'US', + * }); + * + * // Get full evaluation details including variant and reason + * const details = await env.FLAGS.getBooleanDetails('my-feature', false); + * console.log(details.variant, details.reason); + * ``` + */ +declare abstract class Flags { + /** + * Get a flag value without type checking. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Optional default value returned when evaluation fails. + * @param context Optional evaluation context for targeting rules. + */ + get( + flagKey: string, + defaultValue?: unknown, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanValue( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise; + /** + * Get a string flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringValue( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise; + /** + * Get a number flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberValue( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise; + /** + * Get an object flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectValue( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanDetails( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise>; + /** + * Get a string flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringDetails( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise>; + /** + * Get a number flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberDetails( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise>; + /** + * Get an object flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectDetails( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise>; +} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. diff --git a/types/generated-snapshot/experimental/index.ts b/types/generated-snapshot/experimental/index.ts index 6e9a53463b3..84772501fbe 100755 --- a/types/generated-snapshot/experimental/index.ts +++ b/types/generated-snapshot/experimental/index.ts @@ -505,6 +505,7 @@ export interface ExecutionContext { }; tracing?: Tracing; abort(reason?: any): void; + readonly access?: AccessContext; } export type ExportedHandlerFetchHandler< Env = unknown, @@ -4740,6 +4741,27 @@ export declare abstract class Span { get isTraced(): boolean; setAttribute(key: string, value?: boolean | number | string): void; } +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + */ +export type Identity = object; +/** + * Cloudflare Access authentication information for the current request. + */ +export interface AccessContext { + /** + * The audience claim from the Access JWT. This identifies which Access + * application the request matched. + */ + readonly aud: string; + /** + * Fetches the full identity information for the authenticated user. + * + * @returns The subject's identity, if one exists + */ + getIdentity(): Promise; +} // ============ AI Search Error Interfaces ============ export interface AiSearchInternalError extends Error {} export interface AiSearchNotFoundError extends Error {} @@ -13367,6 +13389,140 @@ export declare type EmailExportedHandler = ( env: Env, ctx: ExecutionContext, ) => void | Promise; +/** + * Evaluation context for targeting rules. + * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. + */ +export type EvaluationContext = Record; +export interface EvaluationDetails { + flagKey: string; + value: T; + variant?: string | undefined; + reason?: string | undefined; + errorCode?: string | undefined; + errorMessage?: string | undefined; +} +export interface FlagEvaluationError extends Error {} +/** + * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. + * + * @example + * ```typescript + * // Get a boolean flag value with a default + * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); + * + * // Get a flag value with evaluation context for targeting + * const variant = await env.FLAGS.getStringValue('experiment', 'control', { + * userId: 'user-123', + * country: 'US', + * }); + * + * // Get full evaluation details including variant and reason + * const details = await env.FLAGS.getBooleanDetails('my-feature', false); + * console.log(details.variant, details.reason); + * ``` + */ +export declare abstract class Flags { + /** + * Get a flag value without type checking. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Optional default value returned when evaluation fails. + * @param context Optional evaluation context for targeting rules. + */ + get( + flagKey: string, + defaultValue?: unknown, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanValue( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise; + /** + * Get a string flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringValue( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise; + /** + * Get a number flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberValue( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise; + /** + * Get an object flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectValue( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanDetails( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise>; + /** + * Get a string flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringDetails( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise>; + /** + * Get a number flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberDetails( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise>; + /** + * Get an object flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectDetails( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise>; +} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. diff --git a/types/generated-snapshot/latest/index.d.ts b/types/generated-snapshot/latest/index.d.ts index 20c39772a75..c943c460d3c 100755 --- a/types/generated-snapshot/latest/index.d.ts +++ b/types/generated-snapshot/latest/index.d.ts @@ -481,6 +481,7 @@ interface ExecutionContext { readonly props: Props; cache?: CacheContext; tracing?: Tracing; + readonly access?: AccessContext; } type ExportedHandlerFetchHandler< Env = unknown, @@ -4066,6 +4067,27 @@ declare abstract class Span { get isTraced(): boolean; setAttribute(key: string, value?: boolean | number | string): void; } +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + */ +type Identity = object; +/** + * Cloudflare Access authentication information for the current request. + */ +interface AccessContext { + /** + * The audience claim from the Access JWT. This identifies which Access + * application the request matched. + */ + readonly aud: string; + /** + * Fetches the full identity information for the authenticated user. + * + * @returns The subject's identity, if one exists + */ + getIdentity(): Promise; +} // ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error {} interface AiSearchNotFoundError extends Error {} @@ -12682,6 +12704,140 @@ declare module "cloudflare:email" { }; export { _EmailMessage as EmailMessage }; } +/** + * Evaluation context for targeting rules. + * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. + */ +type EvaluationContext = Record; +interface EvaluationDetails { + flagKey: string; + value: T; + variant?: string | undefined; + reason?: string | undefined; + errorCode?: string | undefined; + errorMessage?: string | undefined; +} +interface FlagEvaluationError extends Error {} +/** + * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. + * + * @example + * ```typescript + * // Get a boolean flag value with a default + * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); + * + * // Get a flag value with evaluation context for targeting + * const variant = await env.FLAGS.getStringValue('experiment', 'control', { + * userId: 'user-123', + * country: 'US', + * }); + * + * // Get full evaluation details including variant and reason + * const details = await env.FLAGS.getBooleanDetails('my-feature', false); + * console.log(details.variant, details.reason); + * ``` + */ +declare abstract class Flags { + /** + * Get a flag value without type checking. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Optional default value returned when evaluation fails. + * @param context Optional evaluation context for targeting rules. + */ + get( + flagKey: string, + defaultValue?: unknown, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanValue( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise; + /** + * Get a string flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringValue( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise; + /** + * Get a number flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberValue( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise; + /** + * Get an object flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectValue( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanDetails( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise>; + /** + * Get a string flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringDetails( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise>; + /** + * Get a number flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberDetails( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise>; + /** + * Get an object flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectDetails( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise>; +} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. diff --git a/types/generated-snapshot/latest/index.ts b/types/generated-snapshot/latest/index.ts index 9821fd88ad4..d4e7c3539db 100755 --- a/types/generated-snapshot/latest/index.ts +++ b/types/generated-snapshot/latest/index.ts @@ -483,6 +483,7 @@ export interface ExecutionContext { readonly props: Props; cache?: CacheContext; tracing?: Tracing; + readonly access?: AccessContext; } export type ExportedHandlerFetchHandler< Env = unknown, @@ -4072,6 +4073,27 @@ export declare abstract class Span { get isTraced(): boolean; setAttribute(key: string, value?: boolean | number | string): void; } +/** + * Represents the identity of a user authenticated via Cloudflare Access. + * This matches the result of calling /cdn-cgi/access/get-identity. + */ +export type Identity = object; +/** + * Cloudflare Access authentication information for the current request. + */ +export interface AccessContext { + /** + * The audience claim from the Access JWT. This identifies which Access + * application the request matched. + */ + readonly aud: string; + /** + * Fetches the full identity information for the authenticated user. + * + * @returns The subject's identity, if one exists + */ + getIdentity(): Promise; +} // ============ AI Search Error Interfaces ============ export interface AiSearchInternalError extends Error {} export interface AiSearchNotFoundError extends Error {} @@ -12699,6 +12721,140 @@ export declare type EmailExportedHandler = ( env: Env, ctx: ExecutionContext, ) => void | Promise; +/** + * Evaluation context for targeting rules. + * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. + */ +export type EvaluationContext = Record; +export interface EvaluationDetails { + flagKey: string; + value: T; + variant?: string | undefined; + reason?: string | undefined; + errorCode?: string | undefined; + errorMessage?: string | undefined; +} +export interface FlagEvaluationError extends Error {} +/** + * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. + * + * @example + * ```typescript + * // Get a boolean flag value with a default + * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); + * + * // Get a flag value with evaluation context for targeting + * const variant = await env.FLAGS.getStringValue('experiment', 'control', { + * userId: 'user-123', + * country: 'US', + * }); + * + * // Get full evaluation details including variant and reason + * const details = await env.FLAGS.getBooleanDetails('my-feature', false); + * console.log(details.variant, details.reason); + * ``` + */ +export declare abstract class Flags { + /** + * Get a flag value without type checking. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Optional default value returned when evaluation fails. + * @param context Optional evaluation context for targeting rules. + */ + get( + flagKey: string, + defaultValue?: unknown, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanValue( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise; + /** + * Get a string flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringValue( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise; + /** + * Get a number flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberValue( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise; + /** + * Get an object flag value. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectValue( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise; + /** + * Get a boolean flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getBooleanDetails( + flagKey: string, + defaultValue: boolean, + context?: EvaluationContext, + ): Promise>; + /** + * Get a string flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getStringDetails( + flagKey: string, + defaultValue: string, + context?: EvaluationContext, + ): Promise>; + /** + * Get a number flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getNumberDetails( + flagKey: string, + defaultValue: number, + context?: EvaluationContext, + ): Promise>; + /** + * Get an object flag value with full evaluation details. + * @param flagKey The key of the flag to evaluate. + * @param defaultValue Default value returned when evaluation fails or the flag type does not match. + * @param context Optional evaluation context for targeting rules. + */ + getObjectDetails( + flagKey: string, + defaultValue: T, + context?: EvaluationContext, + ): Promise>; +} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. From 898f14f6d0e303c2a4ae47804a56502cfb904a7c Mon Sep 17 00:00:00 2001 From: Matt Provost Date: Tue, 31 Mar 2026 21:17:13 +0000 Subject: [PATCH 2/5] chore: update names Signed-off-by: Matt Provost --- src/workerd/api/global-scope.h | 28 ++++++++++++++++--- src/workerd/io/compatibility-date.capnp | 9 ++++++ types/defines/access.d.ts | 6 ++-- .../experimental/index.d.ts | 8 +++--- .../generated-snapshot/experimental/index.ts | 8 +++--- types/generated-snapshot/latest/index.d.ts | 8 +++--- types/generated-snapshot/latest/index.ts | 8 +++--- 7 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index fec2afd013f..14986545056 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -295,6 +295,20 @@ class ExecutionContext: public jsg::Object { jsg::Optional> getTracing(jsg::Lock& js); + // Called by the host runtime to set the Access context for this request. + // Must be called before the worker's handler is invoked. + void setAccess(jsg::Lock& js, jsg::JsRef value) { + access = kj::mv(value); + } + + jsg::JsValue getAccess(jsg::Lock& js) { + KJ_IF_SOME(a, access) { + return a.getHandle(js); + } + return js.undefined(); + } + + JSG_RESOURCE_TYPE(ExecutionContext, CompatibilityFlags::Reader flags) { JSG_METHOD(waitUntil); JSG_METHOD(passThroughOnException); @@ -306,6 +320,9 @@ class ExecutionContext: public jsg::Object { if (flags.getEnableVersionApi()) { JSG_LAZY_INSTANCE_PROPERTY(version, getVersion); } + if (flags.getEnableCtxAccess()) { + JSG_LAZY_INSTANCE_PROPERTY(access, getAccess); + } // ctx.tracing - user tracing API. The *type* is always visible (so the generated // `Tracing` / `Span` types exist in every compat-date snapshot, not only the @@ -340,13 +357,13 @@ class ExecutionContext: public jsg::Object { readonly key?: string; readonly override?: string; }; - readonly access?: AccessContext; + readonly access?: CloudflareAccessContext; }); } else { JSG_TS_OVERRIDE( { readonly props: Props; readonly exports: Cloudflare.Exports; - readonly access?: AccessContext; + readonly access?: CloudflareAccessContext; }); } } else { @@ -359,12 +376,12 @@ class ExecutionContext: public jsg::Object { readonly key?: string; readonly override?: string; }; - readonly access?: AccessContext; + readonly access?: CloudflareAccessContext; }); } else { JSG_TS_OVERRIDE( { readonly props: Props; - readonly access?: AccessContext; + readonly access?: CloudflareAccessContext; }); } } @@ -373,16 +390,19 @@ class ExecutionContext: public jsg::Object { void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("props", props); tracker.trackField("version", version); + tracker.trackField("access", access); } private: jsg::JsRef exports; jsg::JsRef props; kj::Maybe> version; + kj::Maybe> access; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(props); visitor.visit(version); + visitor.visit(access); } }; diff --git a/src/workerd/io/compatibility-date.capnp b/src/workerd/io/compatibility-date.capnp index 56d8a6d08c8..77147fa9ea6 100644 --- a/src/workerd/io/compatibility-date.capnp +++ b/src/workerd/io/compatibility-date.capnp @@ -1540,4 +1540,13 @@ struct CompatibilityFlags @0x8f8c1b68151b6cef { # startup. This allows packages to extend `sys.path` declaratively (e.g. to # add subdirectories or register import hooks). Without this flag, `.pth` # files in `python_modules/` are ignored. + + enableCtxAccess @177 :Bool + $compatEnableFlag("enable_ctx_access") + $experimental; + # Enables the ctx.access property for Cloudflare Access integration. + # When enabled, ctx.access provides Access authentication context including the + # matched application audience (AUD) and an identity-fetching function. The value + # is set by the host runtime (edgeworker) per-request; in open-source workerd the + # property will always be undefined. } diff --git a/types/defines/access.d.ts b/types/defines/access.d.ts index 818d9541baa..c7442a09995 100644 --- a/types/defines/access.d.ts +++ b/types/defines/access.d.ts @@ -2,12 +2,12 @@ * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. */ -type Identity = object; +type CloudflareAccessIdentity = object; /** * Cloudflare Access authentication information for the current request. */ -interface AccessContext { +interface CloudflareAccessContext { /** * The audience claim from the Access JWT. This identifies which Access * application the request matched. @@ -19,5 +19,5 @@ interface AccessContext { * * @returns The subject's identity, if one exists */ - getIdentity(): Promise; + getIdentity(): Promise; } diff --git a/types/generated-snapshot/experimental/index.d.ts b/types/generated-snapshot/experimental/index.d.ts index 498106cb5d9..bd05999b6ee 100755 --- a/types/generated-snapshot/experimental/index.d.ts +++ b/types/generated-snapshot/experimental/index.d.ts @@ -501,9 +501,9 @@ interface ExecutionContext { readonly key?: string; readonly override?: string; }; + readonly access?: CloudflareAccessContext; tracing?: Tracing; abort(reason?: any): void; - readonly access?: AccessContext; } type ExportedHandlerFetchHandler< Env = unknown, @@ -4739,11 +4739,11 @@ declare abstract class Span { * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. */ -type Identity = object; +type CloudflareAccessIdentity = object; /** * Cloudflare Access authentication information for the current request. */ -interface AccessContext { +interface CloudflareAccessContext { /** * The audience claim from the Access JWT. This identifies which Access * application the request matched. @@ -4754,7 +4754,7 @@ interface AccessContext { * * @returns The subject's identity, if one exists */ - getIdentity(): Promise; + getIdentity(): Promise; } // ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error {} diff --git a/types/generated-snapshot/experimental/index.ts b/types/generated-snapshot/experimental/index.ts index 84772501fbe..b6e7959f54b 100755 --- a/types/generated-snapshot/experimental/index.ts +++ b/types/generated-snapshot/experimental/index.ts @@ -503,9 +503,9 @@ export interface ExecutionContext { readonly key?: string; readonly override?: string; }; + readonly access?: CloudflareAccessContext; tracing?: Tracing; abort(reason?: any): void; - readonly access?: AccessContext; } export type ExportedHandlerFetchHandler< Env = unknown, @@ -4745,11 +4745,11 @@ export declare abstract class Span { * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. */ -export type Identity = object; +export type CloudflareAccessIdentity = object; /** * Cloudflare Access authentication information for the current request. */ -export interface AccessContext { +export interface CloudflareAccessContext { /** * The audience claim from the Access JWT. This identifies which Access * application the request matched. @@ -4760,7 +4760,7 @@ export interface AccessContext { * * @returns The subject's identity, if one exists */ - getIdentity(): Promise; + getIdentity(): Promise; } // ============ AI Search Error Interfaces ============ export interface AiSearchInternalError extends Error {} diff --git a/types/generated-snapshot/latest/index.d.ts b/types/generated-snapshot/latest/index.d.ts index c943c460d3c..0047232b6b6 100755 --- a/types/generated-snapshot/latest/index.d.ts +++ b/types/generated-snapshot/latest/index.d.ts @@ -481,7 +481,7 @@ interface ExecutionContext { readonly props: Props; cache?: CacheContext; tracing?: Tracing; - readonly access?: AccessContext; + readonly access?: CloudflareAccessContext; } type ExportedHandlerFetchHandler< Env = unknown, @@ -4071,11 +4071,11 @@ declare abstract class Span { * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. */ -type Identity = object; +type CloudflareAccessIdentity = object; /** * Cloudflare Access authentication information for the current request. */ -interface AccessContext { +interface CloudflareAccessContext { /** * The audience claim from the Access JWT. This identifies which Access * application the request matched. @@ -4086,7 +4086,7 @@ interface AccessContext { * * @returns The subject's identity, if one exists */ - getIdentity(): Promise; + getIdentity(): Promise; } // ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error {} diff --git a/types/generated-snapshot/latest/index.ts b/types/generated-snapshot/latest/index.ts index d4e7c3539db..5208e3c4636 100755 --- a/types/generated-snapshot/latest/index.ts +++ b/types/generated-snapshot/latest/index.ts @@ -483,7 +483,7 @@ export interface ExecutionContext { readonly props: Props; cache?: CacheContext; tracing?: Tracing; - readonly access?: AccessContext; + readonly access?: CloudflareAccessContext; } export type ExportedHandlerFetchHandler< Env = unknown, @@ -4077,11 +4077,11 @@ export declare abstract class Span { * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. */ -export type Identity = object; +export type CloudflareAccessIdentity = object; /** * Cloudflare Access authentication information for the current request. */ -export interface AccessContext { +export interface CloudflareAccessContext { /** * The audience claim from the Access JWT. This identifies which Access * application the request matched. @@ -4092,7 +4092,7 @@ export interface AccessContext { * * @returns The subject's identity, if one exists */ - getIdentity(): Promise; + getIdentity(): Promise; } // ============ AI Search Error Interfaces ============ export interface AiSearchInternalError extends Error {} From 498eee3136361d570fad6c0bdb31addbbcf0cd5e Mon Sep 17 00:00:00 2001 From: Matt Provost Date: Tue, 7 Apr 2026 23:40:43 +0000 Subject: [PATCH 3/5] chore: address comments Signed-off-by: Matt Provost --- src/workerd/api/global-scope.h | 11 +++++ src/workerd/api/tests/BUILD.bazel | 6 +++ src/workerd/api/tests/ctx-access-test.js | 15 ++++++ src/workerd/api/tests/ctx-access-test.wd-test | 14 ++++++ types/defines/access.d.ts | 36 +++++++++++++- .../experimental/index.d.ts | 47 ++++++++++++++++++- .../generated-snapshot/experimental/index.ts | 47 ++++++++++++++++++- types/generated-snapshot/latest/index.d.ts | 47 ++++++++++++++++++- types/generated-snapshot/latest/index.ts | 47 ++++++++++++++++++- 9 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 src/workerd/api/tests/ctx-access-test.js create mode 100644 src/workerd/api/tests/ctx-access-test.wd-test diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index 14986545056..911f0e2a889 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -297,6 +297,12 @@ class ExecutionContext: public jsg::Object { // Called by the host runtime to set the Access context for this request. // Must be called before the worker's handler is invoked. + // + // Unlike other ExecutionContext fields (props, version, exports) which are injected through the + // constructor, access uses a post-construction setter because the Access context is assembled by + // the host runtime after ExecutionContext construction but before handler invocation. The access + // data (audience claim, identity fetcher) originates from the Cloudflare Access integration + // pipeline and is not available during ExecutionContext construction in edgeworker. void setAccess(jsg::Lock& js, jsg::JsRef value) { access = kj::mv(value); } @@ -346,6 +352,11 @@ class ExecutionContext: public jsg::Object { } // TODO(soon): This is getting unwieldy. + // Note: `access` is included unconditionally in all TS_OVERRIDE branches (unlike `version` + // which is gated by enableVersionApi). This is intentional — adding another conditional would + // double the branch count (from 4 to 8). Since `access` is optional (`?`), the type is + // correct regardless of whether the flag is enabled (the property will be undefined at runtime + // when the flag is off or when setAccess() hasn't been called). if (flags.getEnableCtxExports()) { if (flags.getEnableVersionApi()) { JSG_TS_OVERRIDE( { diff --git a/src/workerd/api/tests/BUILD.bazel b/src/workerd/api/tests/BUILD.bazel index 69f3177f816..6924a3464cb 100644 --- a/src/workerd/api/tests/BUILD.bazel +++ b/src/workerd/api/tests/BUILD.bazel @@ -137,6 +137,12 @@ wd_test( args = ["--experimental"], ) +wd_test( + src = "ctx-access-test.wd-test", + args = ["--experimental"], + data = ["ctx-access-test.js"], +) + wd_test( src = "cache-test.wd-test", args = ["--experimental"], diff --git a/src/workerd/api/tests/ctx-access-test.js b/src/workerd/api/tests/ctx-access-test.js new file mode 100644 index 00000000000..2d4ff2b7592 --- /dev/null +++ b/src/workerd/api/tests/ctx-access-test.js @@ -0,0 +1,15 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { strictEqual } from 'node:assert'; + +export const ctxAccessPropertyExists = { + test(controller, env, ctx) { + // When enable_ctx_access is enabled, the property should exist on ctx + // (as a lazy instance property), even though its value is undefined + // because setAccess() is only called by the host runtime (edgeworker). + strictEqual('access' in ctx, true); + strictEqual(ctx.access, undefined); + }, +}; diff --git a/src/workerd/api/tests/ctx-access-test.wd-test b/src/workerd/api/tests/ctx-access-test.wd-test new file mode 100644 index 00000000000..578e5338703 --- /dev/null +++ b/src/workerd/api/tests/ctx-access-test.wd-test @@ -0,0 +1,14 @@ +using Workerd = import "/workerd/workerd.capnp"; + +const unitTests :Workerd.Config = ( + services = [ + ( name = "ctx-access-test", + worker = ( + modules = [ + (name = "worker", esModule = embed "ctx-access-test.js") + ], + compatibilityFlags = ["nodejs_compat", "experimental", "enable_ctx_access"], + ) + ), + ], +); diff --git a/types/defines/access.d.ts b/types/defines/access.d.ts index c7442a09995..18319ad6269 100644 --- a/types/defines/access.d.ts +++ b/types/defines/access.d.ts @@ -1,8 +1,39 @@ /** * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. */ -type CloudflareAccessIdentity = object; +interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { id: string; type: string }; + /** Geographic information about where the user authenticated. */ + geo?: { country: string }; + /** Group memberships from the identity provider. */ + groups?: Array<{ id: string; name: string; email?: string }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} /** * Cloudflare Access authentication information for the current request. @@ -16,8 +47,11 @@ interface CloudflareAccessContext { /** * Fetches the full identity information for the authenticated user. + * This makes a call to the Access identity service to retrieve extended + * user information such as groups, device posture, and identity provider data. * * @returns The subject's identity, if one exists + * @throws May throw if the identity service is unreachable or returns an error. */ getIdentity(): Promise; } diff --git a/types/generated-snapshot/experimental/index.d.ts b/types/generated-snapshot/experimental/index.d.ts index bd05999b6ee..f3ca483a3ce 100755 --- a/types/generated-snapshot/experimental/index.d.ts +++ b/types/generated-snapshot/experimental/index.d.ts @@ -4738,8 +4738,48 @@ declare abstract class Span { /** * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. - */ -type CloudflareAccessIdentity = object; + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} /** * Cloudflare Access authentication information for the current request. */ @@ -4751,8 +4791,11 @@ interface CloudflareAccessContext { readonly aud: string; /** * Fetches the full identity information for the authenticated user. + * This makes a call to the Access identity service to retrieve extended + * user information such as groups, device posture, and identity provider data. * * @returns The subject's identity, if one exists + * @throws May throw if the identity service is unreachable or returns an error. */ getIdentity(): Promise; } diff --git a/types/generated-snapshot/experimental/index.ts b/types/generated-snapshot/experimental/index.ts index b6e7959f54b..c07488eb166 100755 --- a/types/generated-snapshot/experimental/index.ts +++ b/types/generated-snapshot/experimental/index.ts @@ -4744,8 +4744,48 @@ export declare abstract class Span { /** * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. - */ -export type CloudflareAccessIdentity = object; + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +export interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} /** * Cloudflare Access authentication information for the current request. */ @@ -4757,8 +4797,11 @@ export interface CloudflareAccessContext { readonly aud: string; /** * Fetches the full identity information for the authenticated user. + * This makes a call to the Access identity service to retrieve extended + * user information such as groups, device posture, and identity provider data. * * @returns The subject's identity, if one exists + * @throws May throw if the identity service is unreachable or returns an error. */ getIdentity(): Promise; } diff --git a/types/generated-snapshot/latest/index.d.ts b/types/generated-snapshot/latest/index.d.ts index 0047232b6b6..ea163bf8303 100755 --- a/types/generated-snapshot/latest/index.d.ts +++ b/types/generated-snapshot/latest/index.d.ts @@ -4070,8 +4070,48 @@ declare abstract class Span { /** * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. - */ -type CloudflareAccessIdentity = object; + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} /** * Cloudflare Access authentication information for the current request. */ @@ -4083,8 +4123,11 @@ interface CloudflareAccessContext { readonly aud: string; /** * Fetches the full identity information for the authenticated user. + * This makes a call to the Access identity service to retrieve extended + * user information such as groups, device posture, and identity provider data. * * @returns The subject's identity, if one exists + * @throws May throw if the identity service is unreachable or returns an error. */ getIdentity(): Promise; } diff --git a/types/generated-snapshot/latest/index.ts b/types/generated-snapshot/latest/index.ts index 5208e3c4636..e8578c15ec3 100755 --- a/types/generated-snapshot/latest/index.ts +++ b/types/generated-snapshot/latest/index.ts @@ -4076,8 +4076,48 @@ export declare abstract class Span { /** * Represents the identity of a user authenticated via Cloudflare Access. * This matches the result of calling /cdn-cgi/access/get-identity. - */ -export type CloudflareAccessIdentity = object; + * + * The exact structure of the returned object depends on the identity provider + * configuration for the Access application. The fields below represent commonly + * available properties, but additional provider-specific fields may be present. + */ +export interface CloudflareAccessIdentity extends Record { + /** The user's email address, if available from the identity provider. */ + email?: string; + /** The user's display name. */ + name?: string; + /** The user's unique identifier. */ + user_uuid?: string; + /** The Cloudflare account ID. */ + account_id?: string; + /** Login timestamp (Unix epoch seconds). */ + iat?: number; + /** The user's IP address at authentication time. */ + ip?: string; + /** Authentication methods used (e.g., "pwd"). */ + amr?: string[]; + /** Identity provider information. */ + idp?: { + id: string; + type: string; + }; + /** Geographic information about where the user authenticated. */ + geo?: { + country: string; + }; + /** Group memberships from the identity provider. */ + groups?: Array<{ + id: string; + name: string; + email?: string; + }>; + /** Device posture check results, keyed by check ID. */ + devicePosture?: Record; + /** True if the user connected via Cloudflare WARP. */ + is_warp?: boolean; + /** True if the user is authenticated via Cloudflare Gateway. */ + is_gateway?: boolean; +} /** * Cloudflare Access authentication information for the current request. */ @@ -4089,8 +4129,11 @@ export interface CloudflareAccessContext { readonly aud: string; /** * Fetches the full identity information for the authenticated user. + * This makes a call to the Access identity service to retrieve extended + * user information such as groups, device posture, and identity provider data. * * @returns The subject's identity, if one exists + * @throws May throw if the identity service is unreachable or returns an error. */ getIdentity(): Promise; } From e3128f2af72ed8874a38db037456a2854b572cc0 Mon Sep 17 00:00:00 2001 From: Matt Provost Date: Fri, 17 Apr 2026 16:16:19 +0000 Subject: [PATCH 4/5] chore: copy ctx.cache design Signed-off-by: Matt Provost --- src/workerd/api/global-scope.c++ | 16 +++++ src/workerd/api/global-scope.h | 58 +++++++++---------- src/workerd/api/tests/BUILD.bazel | 1 - src/workerd/api/tests/ctx-access-test.js | 6 +- src/workerd/api/tests/ctx-access-test.wd-test | 2 +- src/workerd/io/compatibility-date.capnp | 9 --- src/workerd/io/worker.c++ | 4 ++ src/workerd/io/worker.h | 5 ++ types/defines/access.d.ts | 21 ------- .../experimental/index.d.ts | 23 ++------ .../generated-snapshot/experimental/index.ts | 23 ++------ types/generated-snapshot/latest/index.d.ts | 25 ++------ types/generated-snapshot/latest/index.ts | 25 ++------ 13 files changed, 75 insertions(+), 143 deletions(-) diff --git a/src/workerd/api/global-scope.c++ b/src/workerd/api/global-scope.c++ index 2f705fe7bbb..7ad141c028a 100644 --- a/src/workerd/api/global-scope.c++ +++ b/src/workerd/api/global-scope.c++ @@ -96,6 +96,22 @@ jsg::Optional> ExecutionContext::getTracing(jsg::Lock& js) { return js.alloc(); } +kj::StringPtr AccessContext::getAud() { + JSG_FAIL_REQUIRE(Error, "Access context is not available."); +} + +jsg::Promise AccessContext::getIdentity(jsg::Lock& js) { + JSG_FAIL_REQUIRE(Error, "Access context is not available."); +} + +jsg::Optional> ExecutionContext::getAccess(jsg::Lock& js) { + // Hook for the embedding application to provide an AccessContext. + // The default Worker::Api implementation returns kj::none. + if (IoContext::hasCurrent()) { + return Worker::Isolate::from(js).getApi().getCtxAccessProperty(js); + } + return kj::none; +} void ExecutionContext::abort(jsg::Lock& js, jsg::Optional reason) { KJ_IF_SOME(r, reason) { IoContext::current().abort(js.exceptionToKj(kj::mv(r))); diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index 911f0e2a889..5ac34f6901d 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -240,6 +240,29 @@ class CacheContext: public jsg::Object { } }; +// Base class for the ctx.access object providing Cloudflare Access authentication context. +// Subclass when embedding to provide an implementation. +class AccessContext: public jsg::Object { + public: + // Returns the audience claim from the Access JWT. + // + // The default implementation throws — only meaningful when overridden by the embedding. + virtual kj::StringPtr getAud(); + + // Fetches the full identity information for the authenticated user. + // + // The default implementation throws — only meaningful when overridden by the embedding. + virtual jsg::Promise getIdentity(jsg::Lock& js); + + JSG_RESOURCE_TYPE(AccessContext) { + JSG_READONLY_INSTANCE_PROPERTY(aud, getAud); + JSG_METHOD(getIdentity); + JSG_TS_OVERRIDE(CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; + }); + } +}; class ExecutionContext: public jsg::Object { public: ExecutionContext(jsg::Lock& js, jsg::JsValue exports) @@ -295,24 +318,9 @@ class ExecutionContext: public jsg::Object { jsg::Optional> getTracing(jsg::Lock& js); - // Called by the host runtime to set the Access context for this request. - // Must be called before the worker's handler is invoked. - // - // Unlike other ExecutionContext fields (props, version, exports) which are injected through the - // constructor, access uses a post-construction setter because the Access context is assembled by - // the host runtime after ExecutionContext construction but before handler invocation. The access - // data (audience claim, identity fetcher) originates from the Cloudflare Access integration - // pipeline and is not available during ExecutionContext construction in edgeworker. - void setAccess(jsg::Lock& js, jsg::JsRef value) { - access = kj::mv(value); - } - - jsg::JsValue getAccess(jsg::Lock& js) { - KJ_IF_SOME(a, access) { - return a.getHandle(js); - } - return js.undefined(); - } + // Returns an AccessContext for the current request, or empty jsg::Optional otherwise. + // Called by the runtime to provide Cloudflare Access authentication context. + jsg::Optional> getAccess(jsg::Lock& js); JSG_RESOURCE_TYPE(ExecutionContext, CompatibilityFlags::Reader flags) { @@ -326,9 +334,7 @@ class ExecutionContext: public jsg::Object { if (flags.getEnableVersionApi()) { JSG_LAZY_INSTANCE_PROPERTY(version, getVersion); } - if (flags.getEnableCtxAccess()) { - JSG_LAZY_INSTANCE_PROPERTY(access, getAccess); - } + JSG_LAZY_INSTANCE_PROPERTY(access, getAccess); // ctx.tracing - user tracing API. The *type* is always visible (so the generated // `Tracing` / `Span` types exist in every compat-date snapshot, not only the @@ -352,11 +358,6 @@ class ExecutionContext: public jsg::Object { } // TODO(soon): This is getting unwieldy. - // Note: `access` is included unconditionally in all TS_OVERRIDE branches (unlike `version` - // which is gated by enableVersionApi). This is intentional — adding another conditional would - // double the branch count (from 4 to 8). Since `access` is optional (`?`), the type is - // correct regardless of whether the flag is enabled (the property will be undefined at runtime - // when the flag is off or when setAccess() hasn't been called). if (flags.getEnableCtxExports()) { if (flags.getEnableVersionApi()) { JSG_TS_OVERRIDE( { @@ -401,19 +402,16 @@ class ExecutionContext: public jsg::Object { void visitForMemoryInfo(jsg::MemoryTracker& tracker) const { tracker.trackField("props", props); tracker.trackField("version", version); - tracker.trackField("access", access); } private: jsg::JsRef exports; jsg::JsRef props; kj::Maybe> version; - kj::Maybe> access; void visitForGc(jsg::GcVisitor& visitor) { visitor.visit(props); visitor.visit(version); - visitor.visit(access); } }; @@ -1124,6 +1122,6 @@ class ServiceWorkerGlobalScope: public WorkerGlobalScope { api::ExecutionContext, api::ExportedHandler, \ api::ServiceWorkerGlobalScope::StructuredCloneOptions, api::Navigator, \ api::AlarmInvocationInfo, api::Immediate, api::Cloudflare, api::CachePurgeError, \ - api::CachePurgeResult, api::CachePurgeOptions, api::CacheContext + api::CachePurgeResult, api::CachePurgeOptions, api::CacheContext, api::AccessContext // The list of global-scope.h types that are added to worker.c++'s JSG_DECLARE_ISOLATE_TYPE } // namespace workerd::api diff --git a/src/workerd/api/tests/BUILD.bazel b/src/workerd/api/tests/BUILD.bazel index 6924a3464cb..1d0b6d91abd 100644 --- a/src/workerd/api/tests/BUILD.bazel +++ b/src/workerd/api/tests/BUILD.bazel @@ -139,7 +139,6 @@ wd_test( wd_test( src = "ctx-access-test.wd-test", - args = ["--experimental"], data = ["ctx-access-test.js"], ) diff --git a/src/workerd/api/tests/ctx-access-test.js b/src/workerd/api/tests/ctx-access-test.js index 2d4ff2b7592..35c6c56d105 100644 --- a/src/workerd/api/tests/ctx-access-test.js +++ b/src/workerd/api/tests/ctx-access-test.js @@ -6,9 +6,9 @@ import { strictEqual } from 'node:assert'; export const ctxAccessPropertyExists = { test(controller, env, ctx) { - // When enable_ctx_access is enabled, the property should exist on ctx - // (as a lazy instance property), even though its value is undefined - // because setAccess() is only called by the host runtime (edgeworker). + // The access property is always present on ctx as a lazy instance property. + // In standalone workerd (no embedding override), the value is undefined because + // the default Worker::Api::getCtxAccessProperty() returns kj::none. strictEqual('access' in ctx, true); strictEqual(ctx.access, undefined); }, diff --git a/src/workerd/api/tests/ctx-access-test.wd-test b/src/workerd/api/tests/ctx-access-test.wd-test index 578e5338703..1c85d9902fe 100644 --- a/src/workerd/api/tests/ctx-access-test.wd-test +++ b/src/workerd/api/tests/ctx-access-test.wd-test @@ -7,7 +7,7 @@ const unitTests :Workerd.Config = ( modules = [ (name = "worker", esModule = embed "ctx-access-test.js") ], - compatibilityFlags = ["nodejs_compat", "experimental", "enable_ctx_access"], + compatibilityFlags = ["nodejs_compat"], ) ), ], diff --git a/src/workerd/io/compatibility-date.capnp b/src/workerd/io/compatibility-date.capnp index 77147fa9ea6..56d8a6d08c8 100644 --- a/src/workerd/io/compatibility-date.capnp +++ b/src/workerd/io/compatibility-date.capnp @@ -1540,13 +1540,4 @@ struct CompatibilityFlags @0x8f8c1b68151b6cef { # startup. This allows packages to extend `sys.path` declaratively (e.g. to # add subdirectories or register import hooks). Without this flag, `.pth` # files in `python_modules/` are ignored. - - enableCtxAccess @177 :Bool - $compatEnableFlag("enable_ctx_access") - $experimental; - # Enables the ctx.access property for Cloudflare Access integration. - # When enabled, ctx.access provides Access authentication context including the - # matched application audience (AUD) and an identity-fetching function. The value - # is set by the host runtime (edgeworker) per-request; in open-source workerd the - # property will always be undefined. } diff --git a/src/workerd/io/worker.c++ b/src/workerd/io/worker.c++ index 2394e065b64..cc1e8d8687b 100644 --- a/src/workerd/io/worker.c++ +++ b/src/workerd/io/worker.c++ @@ -526,6 +526,10 @@ jsg::Optional> Worker::Api::getCtxCacheProperty(jsg: return kj::none; } +jsg::Optional> Worker::Api::getCtxAccessProperty(jsg::Lock& js) const { + return kj::none; +} + struct Worker::Impl { kj::Maybe> context; diff --git a/src/workerd/io/worker.h b/src/workerd/io/worker.h index 8c3c6d1f376..8e43a2e5881 100644 --- a/src/workerd/io/worker.h +++ b/src/workerd/io/worker.h @@ -46,6 +46,7 @@ struct CryptoAlgorithm; struct QueueExportedHandler; class WebSocket; class WebSocketRequestResponsePair; +class AccessContext; class CacheContext; class ExecutionContext; namespace pyodide { @@ -670,6 +671,10 @@ class Worker::Api { // The default implementation returns kj::none (undefined). virtual jsg::Optional> getCtxCacheProperty(jsg::Lock& js) const; + // Hook for the embedding application to provide an AccessContext for the ctx.access property. + // The default implementation returns kj::none (undefined). + virtual jsg::Optional> getCtxAccessProperty(jsg::Lock& js) const; + virtual const jsg::IsolateObserver& getObserver() const = 0; virtual void setIsolateObserver(IsolateObserver&) = 0; diff --git a/types/defines/access.d.ts b/types/defines/access.d.ts index 18319ad6269..db2b1a5307e 100644 --- a/types/defines/access.d.ts +++ b/types/defines/access.d.ts @@ -34,24 +34,3 @@ interface CloudflareAccessIdentity extends Record { /** True if the user is authenticated via Cloudflare Gateway. */ is_gateway?: boolean; } - -/** - * Cloudflare Access authentication information for the current request. - */ -interface CloudflareAccessContext { - /** - * The audience claim from the Access JWT. This identifies which Access - * application the request matched. - */ - readonly aud: string; - - /** - * Fetches the full identity information for the authenticated user. - * This makes a call to the Access identity service to retrieve extended - * user information such as groups, device posture, and identity provider data. - * - * @returns The subject's identity, if one exists - * @throws May throw if the identity service is unreachable or returns an error. - */ - getIdentity(): Promise; -} diff --git a/types/generated-snapshot/experimental/index.d.ts b/types/generated-snapshot/experimental/index.d.ts index f3ca483a3ce..c6ce07680bc 100755 --- a/types/generated-snapshot/experimental/index.d.ts +++ b/types/generated-snapshot/experimental/index.d.ts @@ -605,6 +605,10 @@ interface CachePurgeOptions { interface CacheContext { purge(options: CachePurgeOptions): Promise; } +interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} declare abstract class ColoLocalActorNamespace { get(actorId: string): Fetcher; } @@ -4780,25 +4784,6 @@ interface CloudflareAccessIdentity extends Record { /** True if the user is authenticated via Cloudflare Gateway. */ is_gateway?: boolean; } -/** - * Cloudflare Access authentication information for the current request. - */ -interface CloudflareAccessContext { - /** - * The audience claim from the Access JWT. This identifies which Access - * application the request matched. - */ - readonly aud: string; - /** - * Fetches the full identity information for the authenticated user. - * This makes a call to the Access identity service to retrieve extended - * user information such as groups, device posture, and identity provider data. - * - * @returns The subject's identity, if one exists - * @throws May throw if the identity service is unreachable or returns an error. - */ - getIdentity(): Promise; -} // ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error {} interface AiSearchNotFoundError extends Error {} diff --git a/types/generated-snapshot/experimental/index.ts b/types/generated-snapshot/experimental/index.ts index c07488eb166..40167a0fe36 100755 --- a/types/generated-snapshot/experimental/index.ts +++ b/types/generated-snapshot/experimental/index.ts @@ -607,6 +607,10 @@ export interface CachePurgeOptions { export interface CacheContext { purge(options: CachePurgeOptions): Promise; } +export interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} export declare abstract class ColoLocalActorNamespace { get(actorId: string): Fetcher; } @@ -4786,25 +4790,6 @@ export interface CloudflareAccessIdentity extends Record { /** True if the user is authenticated via Cloudflare Gateway. */ is_gateway?: boolean; } -/** - * Cloudflare Access authentication information for the current request. - */ -export interface CloudflareAccessContext { - /** - * The audience claim from the Access JWT. This identifies which Access - * application the request matched. - */ - readonly aud: string; - /** - * Fetches the full identity information for the authenticated user. - * This makes a call to the Access identity service to retrieve extended - * user information such as groups, device posture, and identity provider data. - * - * @returns The subject's identity, if one exists - * @throws May throw if the identity service is unreachable or returns an error. - */ - getIdentity(): Promise; -} // ============ AI Search Error Interfaces ============ export interface AiSearchInternalError extends Error {} export interface AiSearchNotFoundError extends Error {} diff --git a/types/generated-snapshot/latest/index.d.ts b/types/generated-snapshot/latest/index.d.ts index ea163bf8303..1842bbbcff5 100755 --- a/types/generated-snapshot/latest/index.d.ts +++ b/types/generated-snapshot/latest/index.d.ts @@ -480,8 +480,8 @@ interface ExecutionContext { readonly exports: Cloudflare.Exports; readonly props: Props; cache?: CacheContext; - tracing?: Tracing; readonly access?: CloudflareAccessContext; + tracing?: Tracing; } type ExportedHandlerFetchHandler< Env = unknown, @@ -582,6 +582,10 @@ interface CachePurgeOptions { interface CacheContext { purge(options: CachePurgeOptions): Promise; } +interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} declare abstract class ColoLocalActorNamespace { get(actorId: string): Fetcher; } @@ -4112,25 +4116,6 @@ interface CloudflareAccessIdentity extends Record { /** True if the user is authenticated via Cloudflare Gateway. */ is_gateway?: boolean; } -/** - * Cloudflare Access authentication information for the current request. - */ -interface CloudflareAccessContext { - /** - * The audience claim from the Access JWT. This identifies which Access - * application the request matched. - */ - readonly aud: string; - /** - * Fetches the full identity information for the authenticated user. - * This makes a call to the Access identity service to retrieve extended - * user information such as groups, device posture, and identity provider data. - * - * @returns The subject's identity, if one exists - * @throws May throw if the identity service is unreachable or returns an error. - */ - getIdentity(): Promise; -} // ============ AI Search Error Interfaces ============ interface AiSearchInternalError extends Error {} interface AiSearchNotFoundError extends Error {} diff --git a/types/generated-snapshot/latest/index.ts b/types/generated-snapshot/latest/index.ts index e8578c15ec3..32e62d65e9c 100755 --- a/types/generated-snapshot/latest/index.ts +++ b/types/generated-snapshot/latest/index.ts @@ -482,8 +482,8 @@ export interface ExecutionContext { readonly exports: Cloudflare.Exports; readonly props: Props; cache?: CacheContext; - tracing?: Tracing; readonly access?: CloudflareAccessContext; + tracing?: Tracing; } export type ExportedHandlerFetchHandler< Env = unknown, @@ -584,6 +584,10 @@ export interface CachePurgeOptions { export interface CacheContext { purge(options: CachePurgeOptions): Promise; } +export interface CloudflareAccessContext { + readonly aud: string; + getIdentity(): Promise; +} export declare abstract class ColoLocalActorNamespace { get(actorId: string): Fetcher; } @@ -4118,25 +4122,6 @@ export interface CloudflareAccessIdentity extends Record { /** True if the user is authenticated via Cloudflare Gateway. */ is_gateway?: boolean; } -/** - * Cloudflare Access authentication information for the current request. - */ -export interface CloudflareAccessContext { - /** - * The audience claim from the Access JWT. This identifies which Access - * application the request matched. - */ - readonly aud: string; - /** - * Fetches the full identity information for the authenticated user. - * This makes a call to the Access identity service to retrieve extended - * user information such as groups, device posture, and identity provider data. - * - * @returns The subject's identity, if one exists - * @throws May throw if the identity service is unreachable or returns an error. - */ - getIdentity(): Promise; -} // ============ AI Search Error Interfaces ============ export interface AiSearchInternalError extends Error {} export interface AiSearchNotFoundError extends Error {} From 822a3813666e17c101a63a3722ebd1c1f94391b8 Mon Sep 17 00:00:00 2001 From: Matt Provost Date: Wed, 29 Apr 2026 16:20:15 +0000 Subject: [PATCH 5/5] chore: switch to access info this should be more in line with workerd architecture. we separate the polymorphism to something that AccessContext wraps around rather than having AccessContext itself be polymorphic. Signed-off-by: Matt Provost --- src/workerd/api/global-scope.c++ | 24 +++- src/workerd/api/global-scope.h | 31 ++-- src/workerd/api/tests/ctx-access-test.js | 5 +- src/workerd/io/BUILD.bazel | 1 + src/workerd/io/access-info.h | 40 ++++++ src/workerd/io/io-context.c++ | 5 +- src/workerd/io/io-context.h | 18 ++- src/workerd/io/worker-entrypoint.c++ | 22 +-- src/workerd/io/worker-entrypoint.h | 6 +- src/workerd/io/worker.c++ | 4 - src/workerd/io/worker.h | 5 - .../experimental/index.d.ts | 134 ------------------ .../generated-snapshot/experimental/index.ts | 134 ------------------ types/generated-snapshot/latest/index.d.ts | 134 ------------------ types/generated-snapshot/latest/index.ts | 134 ------------------ 15 files changed, 122 insertions(+), 575 deletions(-) create mode 100644 src/workerd/io/access-info.h diff --git a/src/workerd/api/global-scope.c++ b/src/workerd/api/global-scope.c++ index 7ad141c028a..760464a0eb0 100644 --- a/src/workerd/api/global-scope.c++ +++ b/src/workerd/api/global-scope.c++ @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -97,18 +98,27 @@ jsg::Optional> ExecutionContext::getTracing(jsg::Lock& js) { } kj::StringPtr AccessContext::getAud() { - JSG_FAIL_REQUIRE(Error, "Access context is not available."); + return info->getAudience(); } -jsg::Promise AccessContext::getIdentity(jsg::Lock& js) { - JSG_FAIL_REQUIRE(Error, "Access context is not available."); +jsg::Promise> AccessContext::getIdentity(jsg::Lock& js) { + auto& ioctx = IoContext::current(); + return ioctx.awaitIo(js, info->getIdentity(), + [](jsg::Lock& js, kj::Maybe json) -> jsg::Optional { + KJ_IF_SOME(j, json) { + return jsg::JsValue(js.parseJson(j).getHandle(js)); + } + return kj::none; + }); } jsg::Optional> ExecutionContext::getAccess(jsg::Lock& js) { - // Hook for the embedding application to provide an AccessContext. - // The default Worker::Api implementation returns kj::none. - if (IoContext::hasCurrent()) { - return Worker::Isolate::from(js).getApi().getCtxAccessProperty(js); + // Pull the per-request AccessInfo (if any) off the current IncomingRequest. Standalone workerd + // never supplies one; production embedders construct one before calling newWorkerEntrypoint(). + if (!IoContext::hasCurrent()) return kj::none; + auto& ioctx = IoContext::current(); + KJ_IF_SOME(info, ioctx.getAccessInfo()) { + return js.alloc(ioctx.addObject(kj::addRef(info))); } return kj::none; } diff --git a/src/workerd/api/global-scope.h b/src/workerd/api/global-scope.h index 5ac34f6901d..0f679da3456 100644 --- a/src/workerd/api/global-scope.h +++ b/src/workerd/api/global-scope.h @@ -25,6 +25,10 @@ namespace workerd::jsg { class DOMException; } // namespace workerd::jsg +namespace workerd { +class AccessInfo; +} // namespace workerd + namespace workerd::api { class Tracing; @@ -240,19 +244,24 @@ class CacheContext: public jsg::Object { } }; -// Base class for the ctx.access object providing Cloudflare Access authentication context. -// Subclass when embedding to provide an implementation. +// Concrete wrapper exposing per-request Cloudflare Access authentication info to JavaScript +// as `ctx.access`. The actual auth data is supplied by the embedding application via +// `workerd::AccessInfo`, which is plumbed through `newWorkerEntrypoint()` onto +// `IoContext::IncomingRequest`. +// +// Standalone workerd never constructs one of these (no `AccessInfo` is supplied), so +// `ctx.access` is `undefined`. Embedders construct a concrete `AccessInfo` subclass and pass it +// through the entrypoint; `ExecutionContext::getAccess()` lazily wraps it in this class. class AccessContext: public jsg::Object { public: + explicit AccessContext(IoOwn info): info(kj::mv(info)) {} + // Returns the audience claim from the Access JWT. - // - // The default implementation throws — only meaningful when overridden by the embedding. - virtual kj::StringPtr getAud(); + kj::StringPtr getAud(); - // Fetches the full identity information for the authenticated user. - // - // The default implementation throws — only meaningful when overridden by the embedding. - virtual jsg::Promise getIdentity(jsg::Lock& js); + // Fetches the full identity information for the authenticated user. Resolves to `undefined` + // if no identity is associated with the request (e.g. service-token authentication). + jsg::Promise> getIdentity(jsg::Lock& js); JSG_RESOURCE_TYPE(AccessContext) { JSG_READONLY_INSTANCE_PROPERTY(aud, getAud); @@ -262,6 +271,9 @@ class AccessContext: public jsg::Object { getIdentity(): Promise; }); } + + private: + IoOwn info; }; class ExecutionContext: public jsg::Object { public: @@ -322,7 +334,6 @@ class ExecutionContext: public jsg::Object { // Called by the runtime to provide Cloudflare Access authentication context. jsg::Optional> getAccess(jsg::Lock& js); - JSG_RESOURCE_TYPE(ExecutionContext, CompatibilityFlags::Reader flags) { JSG_METHOD(waitUntil); JSG_METHOD(passThroughOnException); diff --git a/src/workerd/api/tests/ctx-access-test.js b/src/workerd/api/tests/ctx-access-test.js index 35c6c56d105..a1e8c8ed7bb 100644 --- a/src/workerd/api/tests/ctx-access-test.js +++ b/src/workerd/api/tests/ctx-access-test.js @@ -7,8 +7,9 @@ import { strictEqual } from 'node:assert'; export const ctxAccessPropertyExists = { test(controller, env, ctx) { // The access property is always present on ctx as a lazy instance property. - // In standalone workerd (no embedding override), the value is undefined because - // the default Worker::Api::getCtxAccessProperty() returns kj::none. + // In standalone workerd no AccessInfo is supplied to newWorkerEntrypoint(), so the + // current IncomingRequest has no AccessInfo and getAccess() returns kj::none, which + // surfaces as `undefined` to JS. strictEqual('access' in ctx, true); strictEqual(ctx.access, undefined); }, diff --git a/src/workerd/io/BUILD.bazel b/src/workerd/io/BUILD.bazel index 43ca28d5262..21273397149 100644 --- a/src/workerd/io/BUILD.bazel +++ b/src/workerd/io/BUILD.bazel @@ -47,6 +47,7 @@ wd_cc_library( "worker-fs.c++", ] + ["//src/workerd/api:srcs"], hdrs = [ + "access-info.h", "compatibility-date.h", "external-pusher.h", "hibernation-manager.h", diff --git a/src/workerd/io/access-info.h b/src/workerd/io/access-info.h new file mode 100644 index 00000000000..57d1223dc83 --- /dev/null +++ b/src/workerd/io/access-info.h @@ -0,0 +1,40 @@ +// Copyright (c) 2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +#pragma once + +#include +#include +#include + +namespace workerd { + +// Per-request Cloudflare Access authentication information. +// +// This is the I/O-side carrier for Access auth data. It is created by the embedding application +// (e.g. the production runtime) before invoking the worker, plumbed through `newWorkerEntrypoint()` +// into the `IoContext::IncomingRequest`, and surfaced to JavaScript by the concrete +// `api::AccessContext` wrapper as `ctx.access`. +// +// In standalone workerd this is never constructed; `ctx.access` evaluates to `undefined`. +// +// This type intentionally lives in `io/` rather than `api/` because: +// - It is the polymorphism boundary between embedders (workerd vs. production), not the +// JS-facing type. +// - It carries per-request data that flows through `newWorkerEntrypoint` → `IncomingRequest`, +// not through `Worker::Api` (which is per-isolate) or `IoChannelFactory`. +class AccessInfo: public kj::Refcounted { + public: + virtual ~AccessInfo() noexcept(false) = default; + + // The audience claim from the Access JWT. Stable for the lifetime of the request. + virtual kj::StringPtr getAudience() = 0; + + // Fetches the full identity information for the authenticated user, equivalent to calling + // /cdn-cgi/access/get-identity. The returned string is a JSON document; `kj::none` indicates + // no identity is available (e.g. service-token authentication). + virtual kj::Promise> getIdentity() = 0; +}; + +} // namespace workerd diff --git a/src/workerd/io/io-context.c++ b/src/workerd/io/io-context.c++ index ac35d319934..75cb1deca6c 100644 --- a/src/workerd/io/io-context.c++ +++ b/src/workerd/io/io-context.c++ @@ -4,6 +4,7 @@ #include "io-context.h" +#include #include #include #include @@ -218,11 +219,13 @@ IoContext::IncomingRequest::IoContext_IncomingRequest(kj::Own context kj::Own ioChannelFactoryParam, kj::Own metricsParam, kj::Maybe> workerTracer, - kj::Maybe maybeTriggerInvocationSpan) + kj::Maybe maybeTriggerInvocationSpan, + kj::Maybe> accessInfo) : context(kj::mv(contextParam)), metrics(kj::mv(metricsParam)), workerTracer(kj::mv(workerTracer)), ioChannelFactory(kj::mv(ioChannelFactoryParam)), + accessInfo(kj::mv(accessInfo)), maybeTriggerInvocationSpan(kj::mv(maybeTriggerInvocationSpan)) {} tracing::InvocationSpanContext& IoContext::IncomingRequest::getInvocationSpanContext() { diff --git a/src/workerd/io/io-context.h b/src/workerd/io/io-context.h index 34300da17b9..df522a74baf 100644 --- a/src/workerd/io/io-context.h +++ b/src/workerd/io/io-context.h @@ -8,6 +8,7 @@ #include "worker.h" #include +#include #include #include #include @@ -71,7 +72,8 @@ class IoContext_IncomingRequest final { kj::Own ioChannelFactory, kj::Own metrics, kj::Maybe> workerTracer, - kj::Maybe maybeTriggerInvocationSpan); + kj::Maybe maybeTriggerInvocationSpan, + kj::Maybe> accessInfo = kj::none); KJ_DISALLOW_COPY_AND_MOVE(IoContext_IncomingRequest); ~IoContext_IncomingRequest() noexcept(false); @@ -131,6 +133,12 @@ class IoContext_IncomingRequest final { return rootUserTraceSpan.addRef(); } + // The Cloudflare Access auth info for this request, if any was provided by the embedder. Used + // to populate `ctx.access` in JavaScript. + kj::Maybe getAccessInfo() { + return accessInfo.map([](kj::Own& p) -> AccessInfo& { return *p; }); + } + // The invocation span context is a unique identifier for a specific // worker invocation. tracing::InvocationSpanContext& getInvocationSpanContext(); @@ -140,6 +148,7 @@ class IoContext_IncomingRequest final { kj::Own metrics; kj::Maybe> workerTracer; kj::Own ioChannelFactory; + kj::Maybe> accessInfo; // Root user trace span for this request. Populated during delivered() via // BaseTracer::makeUserRequestSpan(); otherwise a null SpanParent. The tracer it references @@ -240,6 +249,13 @@ class IoContext final: public kj::Refcounted, private kj::TaskSet::ErrorHandler return getCurrentIncomingRequest().getRootUserTraceSpan(); } + // The Cloudflare Access auth info for the current incoming request, if any was provided by + // the embedder. Used to populate `ctx.access` in JavaScript. + kj::Maybe getAccessInfo() { + if (incomingRequests.empty()) return kj::none; + return getCurrentIncomingRequest().getAccessInfo(); + } + LimitEnforcer& getLimitEnforcer() { return *limitEnforcer; } diff --git a/src/workerd/io/worker-entrypoint.c++ b/src/workerd/io/worker-entrypoint.c++ index 8afb03fda5f..5898800005e 100644 --- a/src/workerd/io/worker-entrypoint.c++ +++ b/src/workerd/io/worker-entrypoint.c++ @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,8 @@ class WorkerEntrypoint final: public WorkerInterface { kj::Maybe cfBlobJson, kj::Maybe versionInfo, kj::Maybe maybeTriggerInvocationSpan, - bool isDynamicDispatch); + bool isDynamicDispatch, + kj::Maybe> accessInfo); kj::Promise request(kj::HttpMethod method, kj::StringPtr url, @@ -110,7 +112,8 @@ class WorkerEntrypoint final: public WorkerInterface { kj::Own ioChannelFactory, kj::Own metrics, kj::Maybe> workerTracer, - kj::Maybe maybeTriggerInvocationSpan); + kj::Maybe maybeTriggerInvocationSpan, + kj::Maybe> accessInfo); template kj::Promise maybeAddGcPassForTest(IoContext& context, kj::Promise promise); @@ -184,7 +187,8 @@ kj::Own WorkerEntrypoint::construct(ThreadContext& threadContex kj::Maybe cfBlobJson, kj::Maybe versionInfo, kj::Maybe maybeTriggerInvocationSpan, - bool isDynamicDispatch) { + bool isDynamicDispatch, + kj::Maybe> accessInfo) { TRACE_EVENT("workerd", "WorkerEntrypoint::construct()"); auto obj = kj::heap(kj::Badge(), threadContext, @@ -192,7 +196,7 @@ kj::Own WorkerEntrypoint::construct(ThreadContext& threadContex kj::mv(cfBlobJson), kj::mv(versionInfo)); obj->init(kj::mv(worker), kj::mv(actor), kj::mv(limitEnforcer), kj::mv(ioContextDependency), kj::mv(ioChannelFactory), kj::addRef(*metrics), kj::mv(workerTracer), - kj::mv(maybeTriggerInvocationSpan)); + kj::mv(maybeTriggerInvocationSpan), kj::mv(accessInfo)); auto& wrapper = metrics->wrapWorkerInterface(*obj); return kj::attachRef(wrapper, kj::mv(obj), kj::mv(metrics)); } @@ -222,7 +226,8 @@ void WorkerEntrypoint::init(kj::Own worker, kj::Own ioChannelFactory, kj::Own metrics, kj::Maybe> workerTracer, - kj::Maybe maybeTriggerInvocationSpan) { + kj::Maybe maybeTriggerInvocationSpan, + kj::Maybe> accessInfo) { TRACE_EVENT("workerd", "WorkerEntrypoint::init()"); // We need to construct the IoContext -- unless this is an actor and it already has a // IoContext, in which case we reuse it. @@ -252,7 +257,7 @@ void WorkerEntrypoint::init(kj::Own worker, } incomingRequest = kj::heap(kj::mv(context), kj::mv(ioChannelFactory), - kj::mv(metrics), kj::mv(workerTracer), kj::mv(maybeTriggerInvocationSpan)) + kj::mv(metrics), kj::mv(workerTracer), kj::mv(maybeTriggerInvocationSpan), kj::mv(accessInfo)) .attach(kj::mv(actor)); } @@ -1008,12 +1013,13 @@ kj::Own newWorkerEntrypoint(ThreadContext& threadContext, kj::Maybe cfBlobJson, kj::Maybe versionInfo, kj::Maybe maybeTriggerInvocationSpan, - bool isDynamicDispatch) { + bool isDynamicDispatch, + kj::Maybe> accessInfo) { return WorkerEntrypoint::construct(threadContext, kj::mv(worker), kj::mv(entrypointName), kj::mv(props), kj::mv(actor), kj::mv(limitEnforcer), kj::mv(ioContextDependency), kj::mv(ioChannelFactory), kj::mv(metrics), waitUntilTasks, tunnelExceptions, kj::mv(workerTracer), kj::mv(cfBlobJson), kj::mv(versionInfo), - kj::mv(maybeTriggerInvocationSpan), isDynamicDispatch); + kj::mv(maybeTriggerInvocationSpan), isDynamicDispatch, kj::mv(accessInfo)); } } // namespace workerd diff --git a/src/workerd/io/worker-entrypoint.h b/src/workerd/io/worker-entrypoint.h index e7ed1c1dfa8..f0cfd1b6498 100644 --- a/src/workerd/io/worker-entrypoint.h +++ b/src/workerd/io/worker-entrypoint.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -47,6 +48,9 @@ kj::Own newWorkerEntrypoint(ThreadContext& threadContext, // subtask of another request. If it is kj::none, then this invocation is a top-level // invocation. kj::Maybe maybeTriggerInvocationSpan = kj::none, - bool isDynamicDispatch = false); + bool isDynamicDispatch = false, + // Per-request Cloudflare Access info. Supplied by the embedding application; standalone + // workerd passes kj::none, which causes `ctx.access` to be `undefined` in JS. + kj::Maybe> accessInfo = kj::none); } // namespace workerd diff --git a/src/workerd/io/worker.c++ b/src/workerd/io/worker.c++ index cc1e8d8687b..2394e065b64 100644 --- a/src/workerd/io/worker.c++ +++ b/src/workerd/io/worker.c++ @@ -526,10 +526,6 @@ jsg::Optional> Worker::Api::getCtxCacheProperty(jsg: return kj::none; } -jsg::Optional> Worker::Api::getCtxAccessProperty(jsg::Lock& js) const { - return kj::none; -} - struct Worker::Impl { kj::Maybe> context; diff --git a/src/workerd/io/worker.h b/src/workerd/io/worker.h index 8e43a2e5881..8c3c6d1f376 100644 --- a/src/workerd/io/worker.h +++ b/src/workerd/io/worker.h @@ -46,7 +46,6 @@ struct CryptoAlgorithm; struct QueueExportedHandler; class WebSocket; class WebSocketRequestResponsePair; -class AccessContext; class CacheContext; class ExecutionContext; namespace pyodide { @@ -671,10 +670,6 @@ class Worker::Api { // The default implementation returns kj::none (undefined). virtual jsg::Optional> getCtxCacheProperty(jsg::Lock& js) const; - // Hook for the embedding application to provide an AccessContext for the ctx.access property. - // The default implementation returns kj::none (undefined). - virtual jsg::Optional> getCtxAccessProperty(jsg::Lock& js) const; - virtual const jsg::IsolateObserver& getObserver() const = 0; virtual void setIsolateObserver(IsolateObserver&) = 0; diff --git a/types/generated-snapshot/experimental/index.d.ts b/types/generated-snapshot/experimental/index.d.ts index c6ce07680bc..a3368baeaf2 100755 --- a/types/generated-snapshot/experimental/index.d.ts +++ b/types/generated-snapshot/experimental/index.d.ts @@ -13400,140 +13400,6 @@ declare module "cloudflare:email" { }; export { _EmailMessage as EmailMessage }; } -/** - * Evaluation context for targeting rules. - * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. - */ -type EvaluationContext = Record; -interface EvaluationDetails { - flagKey: string; - value: T; - variant?: string | undefined; - reason?: string | undefined; - errorCode?: string | undefined; - errorMessage?: string | undefined; -} -interface FlagEvaluationError extends Error {} -/** - * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. - * - * @example - * ```typescript - * // Get a boolean flag value with a default - * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); - * - * // Get a flag value with evaluation context for targeting - * const variant = await env.FLAGS.getStringValue('experiment', 'control', { - * userId: 'user-123', - * country: 'US', - * }); - * - * // Get full evaluation details including variant and reason - * const details = await env.FLAGS.getBooleanDetails('my-feature', false); - * console.log(details.variant, details.reason); - * ``` - */ -declare abstract class Flags { - /** - * Get a flag value without type checking. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Optional default value returned when evaluation fails. - * @param context Optional evaluation context for targeting rules. - */ - get( - flagKey: string, - defaultValue?: unknown, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanValue( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise; - /** - * Get a string flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringValue( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise; - /** - * Get a number flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberValue( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise; - /** - * Get an object flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectValue( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanDetails( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise>; - /** - * Get a string flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringDetails( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise>; - /** - * Get a number flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberDetails( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise>; - /** - * Get an object flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectDetails( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise>; -} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. diff --git a/types/generated-snapshot/experimental/index.ts b/types/generated-snapshot/experimental/index.ts index 40167a0fe36..b4cf2da77dd 100755 --- a/types/generated-snapshot/experimental/index.ts +++ b/types/generated-snapshot/experimental/index.ts @@ -13417,140 +13417,6 @@ export declare type EmailExportedHandler = ( env: Env, ctx: ExecutionContext, ) => void | Promise; -/** - * Evaluation context for targeting rules. - * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. - */ -export type EvaluationContext = Record; -export interface EvaluationDetails { - flagKey: string; - value: T; - variant?: string | undefined; - reason?: string | undefined; - errorCode?: string | undefined; - errorMessage?: string | undefined; -} -export interface FlagEvaluationError extends Error {} -/** - * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. - * - * @example - * ```typescript - * // Get a boolean flag value with a default - * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); - * - * // Get a flag value with evaluation context for targeting - * const variant = await env.FLAGS.getStringValue('experiment', 'control', { - * userId: 'user-123', - * country: 'US', - * }); - * - * // Get full evaluation details including variant and reason - * const details = await env.FLAGS.getBooleanDetails('my-feature', false); - * console.log(details.variant, details.reason); - * ``` - */ -export declare abstract class Flags { - /** - * Get a flag value without type checking. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Optional default value returned when evaluation fails. - * @param context Optional evaluation context for targeting rules. - */ - get( - flagKey: string, - defaultValue?: unknown, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanValue( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise; - /** - * Get a string flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringValue( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise; - /** - * Get a number flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberValue( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise; - /** - * Get an object flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectValue( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanDetails( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise>; - /** - * Get a string flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringDetails( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise>; - /** - * Get a number flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberDetails( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise>; - /** - * Get an object flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectDetails( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise>; -} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. diff --git a/types/generated-snapshot/latest/index.d.ts b/types/generated-snapshot/latest/index.d.ts index 1842bbbcff5..a06cd84cac9 100755 --- a/types/generated-snapshot/latest/index.d.ts +++ b/types/generated-snapshot/latest/index.d.ts @@ -12732,140 +12732,6 @@ declare module "cloudflare:email" { }; export { _EmailMessage as EmailMessage }; } -/** - * Evaluation context for targeting rules. - * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. - */ -type EvaluationContext = Record; -interface EvaluationDetails { - flagKey: string; - value: T; - variant?: string | undefined; - reason?: string | undefined; - errorCode?: string | undefined; - errorMessage?: string | undefined; -} -interface FlagEvaluationError extends Error {} -/** - * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. - * - * @example - * ```typescript - * // Get a boolean flag value with a default - * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); - * - * // Get a flag value with evaluation context for targeting - * const variant = await env.FLAGS.getStringValue('experiment', 'control', { - * userId: 'user-123', - * country: 'US', - * }); - * - * // Get full evaluation details including variant and reason - * const details = await env.FLAGS.getBooleanDetails('my-feature', false); - * console.log(details.variant, details.reason); - * ``` - */ -declare abstract class Flags { - /** - * Get a flag value without type checking. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Optional default value returned when evaluation fails. - * @param context Optional evaluation context for targeting rules. - */ - get( - flagKey: string, - defaultValue?: unknown, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanValue( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise; - /** - * Get a string flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringValue( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise; - /** - * Get a number flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberValue( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise; - /** - * Get an object flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectValue( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanDetails( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise>; - /** - * Get a string flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringDetails( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise>; - /** - * Get a number flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberDetails( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise>; - /** - * Get an object flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectDetails( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise>; -} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. diff --git a/types/generated-snapshot/latest/index.ts b/types/generated-snapshot/latest/index.ts index 32e62d65e9c..a421ce780ce 100755 --- a/types/generated-snapshot/latest/index.ts +++ b/types/generated-snapshot/latest/index.ts @@ -12749,140 +12749,6 @@ export declare type EmailExportedHandler = ( env: Env, ctx: ExecutionContext, ) => void | Promise; -/** - * Evaluation context for targeting rules. - * Keys are attribute names (e.g. "userId", "country"), values are the attribute values. - */ -export type EvaluationContext = Record; -export interface EvaluationDetails { - flagKey: string; - value: T; - variant?: string | undefined; - reason?: string | undefined; - errorCode?: string | undefined; - errorMessage?: string | undefined; -} -export interface FlagEvaluationError extends Error {} -/** - * Feature flags binding for evaluating feature flags from a Cloudflare Workers script. - * - * @example - * ```typescript - * // Get a boolean flag value with a default - * const enabled = await env.FLAGS.getBooleanValue('my-feature', false); - * - * // Get a flag value with evaluation context for targeting - * const variant = await env.FLAGS.getStringValue('experiment', 'control', { - * userId: 'user-123', - * country: 'US', - * }); - * - * // Get full evaluation details including variant and reason - * const details = await env.FLAGS.getBooleanDetails('my-feature', false); - * console.log(details.variant, details.reason); - * ``` - */ -export declare abstract class Flags { - /** - * Get a flag value without type checking. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Optional default value returned when evaluation fails. - * @param context Optional evaluation context for targeting rules. - */ - get( - flagKey: string, - defaultValue?: unknown, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanValue( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise; - /** - * Get a string flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringValue( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise; - /** - * Get a number flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberValue( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise; - /** - * Get an object flag value. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectValue( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise; - /** - * Get a boolean flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getBooleanDetails( - flagKey: string, - defaultValue: boolean, - context?: EvaluationContext, - ): Promise>; - /** - * Get a string flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getStringDetails( - flagKey: string, - defaultValue: string, - context?: EvaluationContext, - ): Promise>; - /** - * Get a number flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getNumberDetails( - flagKey: string, - defaultValue: number, - context?: EvaluationContext, - ): Promise>; - /** - * Get an object flag value with full evaluation details. - * @param flagKey The key of the flag to evaluate. - * @param defaultValue Default value returned when evaluation fails or the flag type does not match. - * @param context Optional evaluation context for targeting rules. - */ - getObjectDetails( - flagKey: string, - defaultValue: T, - context?: EvaluationContext, - ): Promise>; -} /** * Evaluation context for targeting rules. * Keys are attribute names (e.g. "userId", "country"), values are the attribute values.