@cerbos/embedded
Version:
Client library for interacting with embedded Cerbos policy decision points generated by Cerbos Hub from server-side Node.js and browser-based applications
339 lines • 12.1 kB
TypeScript
import type { Options as CoreOptions, DecisionLogEntry, JWT, SourceAttributes, Value } from "@cerbos/core";
import { Bundle } from "./bundle";
import { Transport } from "./transport";
type LoadResult = {
bundle: Bundle;
error?: undefined;
} | {
bundle?: undefined;
error: LoadError;
};
/**
* Error thrown when a {@link Loader} fails to load an embedded policy decision point bundle.
*
* @public
*/
export declare class LoadError extends Error {
/**
* The error that caused loading the embedded policy decision point bundle to fail.
*/
readonly cause: unknown;
constructor(
/**
* The error that caused loading the embedded policy decision point bundle to fail.
*/
cause: unknown);
}
/**
* WebAssembly binary code of an embedded policy decision point bundle, or a URL or HTTP response from which to stream it.
*
* @public
*/
export type Source = string | URL | ArrayBufferView | ArrayBuffer | Response | WebAssembly.Module | Promise<ArrayBufferView | ArrayBuffer | Response | WebAssembly.Module>;
/**
* Options for creating a new {@link Embedded} client or {@link Loader}.
*
* @public
*/
export interface Options extends Pick<CoreOptions, "headers" | "userAgent"> {
/**
* A function to verify and decode JWTs passed as auxiliary data, returning the JWT payload.
*
* @defaultValue (throw an error when a JWT is passed)
*/
decodeJWTPayload?: DecodeJWTPayload | undefined;
/**
* {@link https://docs.cerbos.dev/cerbos/latest/configuration/engine#default_policy_version | Default policy version} to apply to requests that do not specify one.
*
* @defaultValue `"default"`
*/
defaultPolicyVersion?: string | undefined;
/**
* {@link https://docs.cerbos.dev/cerbos/latest/configuration/engine#globals | Global variables} to pass environment-specific information to policy conditions.
*
* @defaultValue `{}`
*/
globals?: Record<string, Value> | undefined;
/**
* Enable {@link https://docs.cerbos.dev/cerbos/latest/configuration/engine#lenient_scopes | lenient scope search}?
*
* By default, when a request specifies a scope of `a.b.c` then a policy must exist with that exact scope.
* If lenient scope search is enabled, then the policy decision point will fall back to trying scopes `a.b`, `a`, and `""`
* if a policy with scope `a.b.c` does not exist.
*
* @defaultValue `false`
*/
lenientScopeSearch?: boolean | undefined;
/**
* A function returning the current time, to be used when evaluating policy conditions.
*
* @remarks
* The function can either return a {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date | Date} or a number of milliseconds elapsed since the Unix epoch.
*
* @defaultValue `Date.now`
*/
now?: (() => Date | number) | undefined;
/**
* A callback to invoke when the embedded policy decision point bundle has been loaded.
*
* @defaultValue (no-op)
*/
onLoad?: ((metadata: BundleMetadata) => void | Promise<void>) | undefined;
/**
* A callback to invoke when the embedded policy decision point bundle has failed to load.
*
* @defaultValue (no-op)
*/
onError?: ((error: LoadError) => void | Promise<void>) | undefined;
/**
* A callback to invoke when a decision is made by the embedded policy decision point.
*
* @defaultValue (no-op)
*/
onDecision?: ((entry: DecisionLogEntry) => void | Promise<void>) | undefined;
}
/**
* A function to verify and decode a JWT, returning its payload.
*
* @example
* Using {@link https://www.npmjs.com/package/jose | jose}:
*
* ```typescript
* import type { DecodeJWTPayload, DecodedJWTPayload } from "@cerbos/embedded";
* import { JWTVerifyGetKey, createRemoteJWKSet, jwtVerify } from "jose";
*
* interface KeySet {
* issuer: string;
* jwks: JWTVerifyGetKey;
* }
*
* const keySets: Record<string, KeySet> = {
* auth0: {
* issuer: "https://example.auth0.com/",
* jwks: createRemoteJWKSet(
* new URL("https://example.auth0.com/.well-known/jwks.json")
* ),
* },
* okta: {
* issuer: "https://example.okta.com/oauth2/default",
* jwks: createRemoteJWKSet(
* new URL("https://example.okta.com/oauth2/default/v1/keys")
* ),
* },
* };
*
* const decodeJWTPayload: DecodeJWTPayload = async ({ token, keySetId }) => {
* if (!keySetId) {
* throw new Error("Missing key set ID");
* }
*
* const keySet = keySets[keySetId];
*
* if (!keySet) {
* throw new Error(`Unknown key set ID "${keySetId}"`);
* }
*
* const { issuer, jwks } = keySet;
*
* const { payload } = await jwtVerify(token, jwks, {
* issuer,
* audience: "https://example.com/",
* });
*
* return payload as DecodedJWTPayload;
* };
* ```
*
* @public
*/
export type DecodeJWTPayload = (jwt: JWT) => DecodedJWTPayload | Promise<DecodedJWTPayload>;
/**
* The decoded payload of a JWT, containing the claims.
*
* @public
*/
export type DecodedJWTPayload = Record<string, Value>;
/**
* Metadata describing an embedded policy decision point bundle.
*
* @public
*/
export interface BundleMetadata {
/**
* The URL from which the bundle was downloaded.
*/
url?: string | undefined;
/**
* The commit SHA from which the bundle was built.
*/
commit: string;
/**
* The time at which the bundle was built.
*/
builtAt: Date;
/**
* The IDs of the policies included in the bundle.
*
* @deprecated Use {@link BundleMetadata.sourceAttributes} instead.
*/
policies: string[];
/**
* Map of the IDs of policies included in the bundle to metadata about their sources.
*/
sourceAttributes: Record<string, SourceAttributes>;
}
/**
* Loads an embedded policy decision point bundle from a given source.
*
* @public
*/
export declare class Loader {
/** @internal */
_active: LoadResult | Promise<LoadResult>;
/** @internal */
readonly _options: Options;
/** @internal */
readonly _userAgent: string;
private readonly logger;
/**
* Load an embedded policy decision point (PDP) bundle from a given source.
*
* @param source - WebAssembly binary code of an embedded PDP bundle, or a URL or HTTP response from which to stream it.
* @param options - additional settings.
*
* @remarks
* Bundle download URLs are available in the "Embedded" section of the "Decision points" page of your Cerbos Hub workspace.
*
* The bundle will be loaded in the background when the loader is created.
* If loading fails, then the first request from the client using the loader will throw an error.
* To detect failure to load the bundle before making any requests, provide an {@link Options.onError} callback or await the {@link Loader.active} method.
*
* @example
* Fetch an embedded PDP bundle via HTTP in a {@link https://caniuse.com/wasm | supported browser} or Node.js:
*
* ```typescript
* const loader = new Loader("https://lite.cerbos.cloud/bundle?workspace=...&label=...");
* ```
*
* @example
* Read an embedded PDP bundle from disk in Node.js:
*
* ```typescript
* import { readFile } from "fs/promises";
*
* const loader = new Loader(readFile("policies.wasm"));
* ```
*
* @example
* Load an embedded PDP bundle from a precompiled WebAssembly module (requires a bundler):
*
* ```typescript
* import bundle from "bundle.wasm";
*
* const loader = new Loader(bundle);
* ```
*/
constructor(source: Source, options?: Options);
/**
* Resolves to the metadata of the loaded bundle, or rejects with the error that was encountered when loading the bundle.
*/
active(): Promise<BundleMetadata>;
/** @internal */
readonly _transport: Transport;
/** @internal */
protected _load(source: Source, url: string | undefined, initial?: boolean): Promise<LoadResult>;
/** @internal */
protected _onLoad(bundle: Bundle, _initial: boolean): Promise<void>;
/** @internal */
protected _onError(error: LoadError): Promise<void>;
}
/**
* Options for creating a new {@link AutoUpdatingLoader}.
*
* @public
*/
export interface AutoUpdateOptions extends Options {
/**
* Whether to activate updated embedded policy decision point bundles as soon as they are downloaded.
*
* @remarks
* If `false`, new bundles will be downloaded automatically but not used to evaluate policy decisions until you call {@link AutoUpdatingLoader.activate}.
* This might be useful if you want to activate updates only on page transitions to avoid layout shifts in your application.
*
* To detect whether an update is available to activate, provide an {@link Options.onLoad} callback or check the {@link AutoUpdatingLoader.pending} property.
*
* @defaultValue `true`
*/
activateOnLoad?: boolean;
/**
* The delay (in milliseconds) between successive requests to check for new embedded policy decision point bundles.
*
* @remarks
* The interval will be increased to the minimum of 10 seconds if a smaller value is specified.
*
* @defaultValue `60_000` (1 minute)
*/
interval?: number;
}
/**
* Loads an embedded policy decision point bundle from a given URL, and polls for updates.
*
* @public
*/
export declare class AutoUpdatingLoader extends Loader {
private readonly url;
private readonly activateOnLoad;
private readonly interval;
private _pending;
private etag;
private running;
private abortController?;
private timeout?;
/**
* Load an embedded policy decision point bundle from a given URL.
*
* @param url - URL from which to stream bundles.
* @param options - additional settings.
*
* @remarks
* Bundle download URLs are available in the "Embedded" section of the "Decision points" page of your Cerbos Hub workspace.
*
* The bundle will be loaded in the background when the loader is created.
* If initial loading fails, then the first request from the client using the loader will throw an error.
* To detect failure to load the bundle before making any requests, provide an {@link Options.onError} callback or await the {@link Loader.active} method.
*
* Failure to load updates after the initial load will not cause requests from the client to throw errors,
* but errors will be passed to the {@link Options.onError} callback.
*/
constructor(url: string | URL, { activateOnLoad, interval, ...options }?: AutoUpdateOptions);
/**
* The metadata of a new embedded policy decision point bundle that has been downloaded but is not yet being used to evaluate policy decisions.
*
* @remarks
* Only set if {@link AutoUpdateOptions.activateOnLoad} is `false` and an update has been downloaded.
*
* Use {@link AutoUpdatingLoader.activate} to start using the pending bundle to evaluate policy decisions.
*/
get pending(): BundleMetadata | undefined;
/**
* Promote the {@link AutoUpdatingLoader.pending | pending} embedded policy decision point bundle (if any) to active, so that it is used to evaluate policy decisions.
*
* @remarks
* This method is a no-op if an update has not been downloaded, or if {@link AutoUpdateOptions.activateOnLoad} is `true` (the default).
*/
activate(): void;
/**
* Stops polling for new embedded policy decision point bundles, and aborts any in-flight updates.
*/
stop(): void;
/** @internal */
protected _onLoad(bundle: Bundle, initial: boolean): Promise<void>;
/** @internal */
protected _onError(error: LoadError): Promise<void>;
private scheduleUpdate;
private update;
private download;
private suppressError;
}
export {};
//# sourceMappingURL=loader.d.ts.map