import { Logger, ComplianceConfig, ApiCredentials } from '@cloudflare/workers-utils'; /** * Pluggable persistence for a typed config blob. * */ interface ConfigStorage { /** * Read and parse the stored config. * * Consumers can treat a `read()` that resolves with `undefined` as * "not logged in / no temporary account / etc.", and leave error handling * for the surfaces that benefit from it. * * @returns `undefined` for the *empty* state — i.e. when the * backing store does not exist yet, or when it exists but no longer * contains usable data (e.g. encrypted-file present but the keyring entry * is missing, ciphertext fails its auth tag, deserialised payload is not * shaped like `T`). All of these collapse to "nothing usable stored * here" from the consumer's perspective, so the default is to return * `undefined` rather than throw. * * @throws *genuine* errors that the consumer needs to act on — typically * filesystem or permission failures (`EACCES`, `EISDIR`, disk full). * Those propagate so the user can see what's wrong, rather than silently * behaving as "not logged in". */ read(): T | undefined; /** Serialize and persist the config. */ write(config: T): void; /** Remove the backing store; returns whether anything existed beforehand. */ clear(): boolean; /** Human-readable location of the backing store, for display and warnings. */ path(): string; } /** * The data that may be read from the on-disk user auth config file. */ interface UserAuthConfig { oauth_token?: string; refresh_token?: string; expiration_time?: string; scopes?: string[]; /** @deprecated - this field was only provided by the deprecated v1 `wrangler config` command. */ api_token?: string; } type AuthConfigStorage = ConfigStorage; /** * A short-lived "temporary preview account" */ type TemporaryPreviewAccount = { account: { id: string; name: string; apiToken: string; expiresAt: string; }; claim: { url: string; expiresAt: string; }; }; type TemporaryAccountStorage = ConfigStorage; interface GenerateAuthUrlProps { authUrl: string; clientId: string; scopes: string[]; stateQueryParam: string; codeChallenge: string; redirectUri: string; } /** * Build the OAuth 2.0 authorize URL for the Cloudflare auth endpoint. * * Extracted from the rest of the OAuth flow so consumers (or tests) can * substitute a deterministic implementation when a stable URL is needed * (e.g. for snapshot testing). */ declare const generateAuthUrl: ({ authUrl, clientId, scopes, stateQueryParam, codeChallenge, redirectUri, }: GenerateAuthUrlProps) => string; /** * Generates random state to be passed for anti-csrf. * * Extracted from the rest of the OAuth flow so consumers (or tests) can * substitute a deterministic implementation when a stable state value is * needed (e.g. for snapshot testing). */ declare function generateRandomState(lengthOfState: number): string; /** * The dependencies the OAuth flow needs to mint/reuse a short-lived "temporary * preview account" */ interface OAuthFlowTemporaryContext { /** Persistence backend for the cached temporary preview account. */ storage: TemporaryAccountStorage; /** * Hook to customise the terms-acceptance interactive prompt * - question: the question to ask a user in interactive mode. * return answer === "yes" (must be the literal string) * - notice: the notice to print on stderr if in non-interactive mode * always return true */ prompt: (question: string, notice: string) => Promise; } /** * The branded OAuth consent pages the provider redirects the browser to after * the user grants or denies consent. */ interface OAuthConsentPages { /** Redirect target shown after the user grants consent. */ granted: { url: string; }; /** Redirect target shown after the user denies consent, plus the error * surfaced to the terminal. */ denied: { url: string; error: string; }; } /** * Subset of the wrangler `logger` singleton used by the OAuth flow. * Consumers pass in an implementation that maps to their own logging surface. */ type OAuthFlowLogger = Logger; /** * Dependency-injection surface for {@link createOAuthFlow}. * * The OAuth flow only talks to OAuth endpoints (`/oauth2/auth`, `/oauth2/token`, * `/oauth2/revoke`) using `undici`'s `fetch` directly — there is no Cloudflare * API client wired into this context. */ interface OAuthFlowContext { logger: OAuthFlowLogger; /** * Whether the process should not prompt the user. The OAuth flow uses this to * decide whether to short-circuit interactive login attempts. */ isNonInteractiveOrCI: () => boolean; /** * Open the given URL in the user's default browser. Called during the * interactive OAuth login flow with the authorize URL. */ openInBrowser: (url: string) => Promise; /** * Whether environment-based credentials are present. When `true`, the OAuth * flow short-circuits because the env credentials take priority over stored * OAuth tokens: * - `login` refuses to start * - `logout` is a no-op (it cannot revoke env credentials) * - the refresh check returns `false` so an expired stored OAuth token does * not trigger a needless refresh attempt */ hasEnvCredentials: () => boolean; /** * Called after a successful `login` or `logout` so the consumer can invalidate * any caches that depend on the active token (e.g. wrangler's selected-account * cache). */ purgeOnLoginOrLogout?: () => void; /** * The OAuth client ID identifying the consuming CLI to the Cloudflare OAuth * server. Consumer-specific (each CLI registers its own OAuth app), so it is * required. Pass a function to resolve it lazily — e.g. so an env-var read at * call time can switch between production and staging apps. */ clientId: string | (() => string); /** * The branded consent pages the provider redirects to after the user grants * or denies consent. */ consent: OAuthConsentPages; /** * The consuming CLI's branded name (e.g. `"Wrangler"`, `"cf"`), interpolated * into the copy the flow prints to the user — "To authorize , please * visit ...". Consumer-specific, so it is required. */ displayName: string; /** * The command that restarts the device authorization flow (e.g. * `"wrangler login --device"`), quoted when a device code is denied, * expires, or the flow times out. Consumer-specific, so it is required. */ deviceLoginCommand: string; /** * The `redirect_uri` registered on the consumer's OAuth app */ redirectUri: string; /** * Factory that returns a persistence backend for the given auth profile. * * Called with the active profile name (e.g. `"default"`) on every storage * access so the flow always reads/writes the correct backing store. * The consumer is responsible for mapping profile names to concrete storage * backends (e.g. wrangler maps each profile to a separate TOML file under * the global config directory). * * Consumers that want OS-keyring-backed encryption pass the * `storageFactory` from {@link createCredentialStorageContext} (in the * `credential-store` module) — for each profile that adapter resolves * between the plaintext TOML file and the encrypted-file-with-keyring-key * implementation on every call, so runtime preference changes * (`--use-keyring` / `CLOUDFLARE_AUTH_USE_KEYRING`) take effect without * rebuilding the flow. */ storageFactory: (profile?: string) => AuthConfigStorage; /** * Whether the flow's credential resolvers (`getAPIToken` / `requireApiToken`) * should honour the global API key + email pair in addition to scoped API * tokens. */ allowGlobalAuthKey: boolean; /** * Dependencies for minting/reusing a temporary preview account. */ temporary: OAuthFlowTemporaryContext | undefined; /** * Override the OAuth authorize URL generator. Used by tests to produce a * deterministic URL for snapshot testing. Defaults to the standard * implementation. */ generateAuthUrl?: typeof generateAuthUrl; /** * Override the random state generator. Used by tests to produce a * deterministic state value for snapshot testing. Defaults to the standard * implementation. */ generateRandomState?: typeof generateRandomState; } /** * Reason why {@link OAuthFlowAPI.loginOrRefreshIfRequired} could not * authenticate the user. */ type LoginOrRefreshFailureReason = /** no stored credentials and the environment is non-interactive (CI, piped stdin, etc.) so a browser login cannot be started. */ "no-credentials-non-interactive" /** stored credentials and the interactive login attempt was unsuccessful (user cancelled, etc.). */ | "no-credentials-login-failed" /** the stored token has expired, refresh failed, and the environment is non-interactive so a browser login cannot be started. */ | "token-expired-non-interactive" /** the stored token has expired, refresh failed, and the interactive login attempt was unsuccessful. */ | "token-expired-login-failed"; /** * Discriminated union returned by {@link OAuthFlowAPI.loginOrRefreshIfRequired}. * * When `loggedIn` is `true` the caller can proceed. When `false`, `reason` * describes why authentication failed so the caller can surface a * targeted error message. */ type LoginOrRefreshResult = { loggedIn: true; } | { loggedIn: false; reason: LoginOrRefreshFailureReason; }; /** * Options for an interactive OAuth login. */ interface LoginProps { complianceConfig: ComplianceConfig; /** * The OAuth scopes to request. The catalog of valid scope keys is * consumer-defined; this package treats scopes as opaque strings. */ scopes: string[]; /** * Whether to open the authorize URL in a browser. Defaults to `true`. * When `false` the URL is printed for the user to copy/paste. */ browser?: boolean; /** Host the local callback server listens on. Defaults to `localhost`. */ callbackHost?: string; /** Port the local callback server listens on. Defaults to `8976`. */ callbackPort?: number; /** * Named auth profile to store the token under. When omitted, the default * profile (`default.toml`) is used. * Only for use by 'auth create', not exposed to the user */ profile?: string; /** * When `true`, authenticate using the OAuth 2.0 Device Authorization Grant * (RFC 8628) instead of the authorization-code-with-PKCE callback flow. The * device flow does not start a local callback server and works in * environments where the consumer's loopback callback URL * ({@link OAuthFlowContext.redirectUri}) is unreachable from the user's * browser (containers, remote SSH sessions, Codespaces). `callbackHost` and * `callbackPort` are ignored when this is set. */ device?: boolean; } /** * Public surface returned by {@link createOAuthFlow}. */ interface OAuthFlowAPI { /** * Set the active auth profile for all subsequent storage lookups. * * Called once at top of command dispatch by the consumer after resolving the * active profile. `"default"` if never called. */ setProfile(profile: string): void; getActiveProfile(): string; /** * Open the authorize URL in the user's browser, wait for the callback to be * hit on the local HTTP server, exchange the code for an access token, and * persist the result to disk. * * Refuses to start when `ctx.hasEnvCredentials()` returns `true`. * Refuses to start when the compliance region is `fedramp_high`. * * When `props.profile` is set, the token is stored under that profile * instead of the active one. This is used by `auth create `. * * @returns `true` on success, `false` when env credentials are present. */ login(props: LoginProps): Promise; /** * Revoke the stored refresh token at the Cloudflare OAuth endpoint and * delete the on-disk auth config file. * * No-op when `ctx.hasEnvCredentials()` returns `true` (env credentials * cannot be revoked). * * When `profile` is passed, operates on that profile instead of the * active one. This is used by `auth delete `. */ logout(profile?: string): Promise; /** * If the user has no stored OAuth token, attempt an interactive login. * If they have one but it is expired, attempt a refresh; if refresh fails, * fall back to an interactive login. * * Scopes are required in case an interactive login is triggered — the * consumer's scope catalog lives outside this package. * * @returns `{ loggedIn: true }` when the user is authenticated (or env * credentials are present). When authentication fails, returns * `{ loggedIn: false, reason }` describing why — see * {@link LoginOrRefreshFailureReason}. */ loginOrRefreshIfRequired(props: LoginProps): Promise; /** * Read the OAuth access token from local state, refreshing it first if * needed. Returns `undefined` when there is no stored OAuth token or the * refresh fails. * * This intentionally does NOT consult env credentials — callers that want * env-or-OAuth resolution should check env first themselves. */ getOAuthTokenFromLocalState(): Promise; /** * Resolve API credentials, preferring an active temporary preview account * (when one has been latched via {@link activateTemporaryAccount}) over the * env / stored-OAuth resolution performed by the shared credential resolver. * * Returns `undefined` when no credentials are available. */ getAPIToken(): ApiCredentials | undefined; /** * Like {@link getAPIToken}, but throws a `UserError` when no credentials are * available. */ requireApiToken(): ApiCredentials; /** * Return the scopes granted to the stored OAuth token for the active * profile, or `undefined` when no OAuth token is stored. */ getScopes(): string[] | undefined; /** * Establish whether `--temporary` is permitted for this invocation. Called * once at command dispatch by the consumer. Also drops any temporary account * latched by a previous dispatch, so that — when multiple commands share a * process (e.g. in tests) — each invocation starts a fresh temporary session. * No-op when the flow was created without a `temporary` context. */ setTemporaryAllowed(allowed: boolean): void; /** * Whether `--temporary` is permitted for this invocation (see * {@link setTemporaryAllowed}). Always `false` without a `temporary` context. */ isTemporaryAllowed(): boolean; /** * The temporary preview account latched for this invocation, or `undefined`. * Only set after {@link activateTemporaryAccount} has run. */ getActiveTemporaryAccount(): TemporaryPreviewAccount | undefined; /** * The sole creator of the temporary-account latch: mint a fresh temporary * preview account (or reuse a cached one), latch it for this invocation, and * return it. Requires a `temporary` context. */ activateTemporaryAccount(): Promise<{ account: TemporaryPreviewAccount; cached: boolean; }>; } /** * Build an instance of the OAuth flow bound to the given context. * * The returned object owns module-private state (the transient OAuth flow * state and the deprecated-v1 warning latch). In practice consumers create * exactly one instance per process. */ declare function createOAuthFlow(ctx: OAuthFlowContext): OAuthFlowAPI; interface ProfileConfigOperations { exists(profile: string): boolean; list(): string[]; delete(profile: string): void; } interface DirectoryBindingsStorage { read(): Record; write(bindings: Record): void; } interface DirectoryBindingOperations extends DirectoryBindingsStorage { activate(profile: string, dir: string): void; deactivate(dir: string): DeactivateDirectoryResult; getProfileForDirectory(startDir: string): string | undefined; getBindingsForProfile(profile: string): string[]; removeAllBindingsForProfile(profile: string): string[]; } interface ProfileStore { configs: ProfileConfigOperations; bindings: DirectoryBindingOperations; resolve(args: { profile?: string; cwd: string; }): string; } interface DeactivateDirectoryResult { removedProfile: string; newResolution: { profile: string | undefined; source: string; }; } declare function createProfileStore(args: { configs: ProfileConfigOperations; bindings: DirectoryBindingsStorage; }): ProfileStore; declare function validateProfileName(name: string): void; /** * Pluggable backend for the persisted OAuth credentials. * * Concrete implementations: * - {@link FileCredentialStore} — the plaintext TOML file at * `/config/.toml`. Used by default and as * a fallback when keyring storage is unavailable. * - {@link EncryptedFileCredentialStore} — an AES-256-GCM-encrypted file at * `/config/.enc`, with the encryption key * stored in the OS keyring via a {@link KeyProvider}. * * Extends the consumer-facing {@link AuthConfigStorage} contract with two * keyring-specific extras: * - `kind`: discriminant so consumers can tell apart "plain file" and * "encrypted file" stores without sniffing for methods. * - `describe()`: human-readable, possibly multi-line description (e.g. * `"Encrypted file (path) with key in macOS Keychain"`) for surfaces * like `wrangler whoami` that want richer copy than the raw `path()`. * * The interface is synchronous so it can be plugged into existing * `AuthConfigStorage` call sites without forcing every caller to become * async. Under the hood, both implementations use synchronous primitives * (subprocess `spawnSync`, `@napi-rs/keyring`'s sync `Entry` class, * `node:crypto` sync APIs, synchronous filesystem calls). */ interface CredentialStore extends AuthConfigStorage { readonly kind: "file" | "encrypted-file"; /** * Human-readable description of where credentials are stored, suitable * for consumers' `whoami`-style output. May be multi-line / richer * than the raw `path()`. */ describe(): string; } /** * On-disk serialization format for a CLI's config/credential files. * * The whole auth layer is CLI-agnostic; each CLI (wrangler, cf, …) picks the * format its files are written in. Wrangler uses `"toml"` for historical * compatibility; newer CLIs (cf) use `"json"`. The format value doubles as the * file extension (no leading dot). */ type FileFormat = "toml" | "json"; export { type AuthConfigStorage as A, type CredentialStore as C, type DeactivateDirectoryResult as D, type FileFormat as F, type LoginOrRefreshFailureReason as L, type OAuthFlowLogger as O, type ProfileConfigOperations as P, type TemporaryPreviewAccount as T, type UserAuthConfig as U, type ConfigStorage as a, type DirectoryBindingOperations as b, type DirectoryBindingsStorage as c, type LoginOrRefreshResult as d, type LoginProps as e, type OAuthFlowContext as f, type ProfileStore as g, createOAuthFlow as h, createProfileStore as i, generateAuthUrl as j, generateRandomState as k, type OAuthConsentPages as l, validateProfileName as v };