import { type H3Event } from "h3"; import { type McpSignupAttribution } from "./mcp-signup-attribution.js"; export declare function isElectron(event: H3Event): boolean; export declare function isMobile(event: H3Event): boolean; export declare function isConfiguredAppOrigin(value: string | undefined): boolean; export declare function getOrigin(event: H3Event, options?: { useForwardedHost?: boolean; }): string; export declare function getAppBasePath(): string; export declare function getAppUrl(event: H3Event, path?: string): string; export declare const NETLIFY_PREVIEW_GOOGLE_OAUTH_CALLBACK_URL = "https://beta.dispatch.agent-native.com/_agent-native/google/callback"; export declare const AGENT_NATIVE_GOOGLE_OAUTH_RELAY_SECRET_ENV = "AGENT_NATIVE_GOOGLE_OAUTH_RELAY_SECRET"; export declare const NETLIFY_PREVIEW_GOOGLE_OAUTH_RELAY_STATE_PREFIX = "agent-native-preview-google-relay."; export declare function getNetlifyPreviewGoogleOAuthCallbackUrl(event: H3Event, path?: string): string | undefined; export declare function isNetlifyPreviewGoogleOAuthCallbackUrl(value: string | undefined): boolean; export declare function isNetlifyPreviewGoogleOAuthRelayState(value: unknown): value is string; export declare function encodeNetlifyPreviewGoogleOAuthRelayState(state: string, callbackUri: string, now?: number): string; export declare function decodeNetlifyPreviewGoogleOAuthRelayState(value: string | undefined, now?: number): { callbackUri: string; state: string; } | null; export declare function wrapNetlifyPreviewGoogleOAuthState(event: H3Event, state: string, callbackPath?: string): string; export type OAuthRedirectUriOptions = { allowRootCallback?: boolean; /** * Keep the callback under the app's own mount instead of the shared * workspace root callback. Use for providers that register the redirect * URI per flow (e.g. MCP dynamic client registration): the callback then * lands on the app that started the flow without a root relay, which * cannot recover the app id from an opaque state. */ appScopedCallback?: boolean; useNetlifyPreviewGoogleOAuthRelay?: boolean; }; /** * Validate a user-supplied `redirect_uri` for OAuth flows. * * Defends against authorization-code interception (RFC 6819 §4.4.1.7): * even though the upstream provider (Google/Atlassian/Zoom) refuses * unregistered redirect URIs, prefix-style registrations and side * registrations on the same host let a malicious caller swap in an * attacker-controlled URI that the provider still accepts. We reject any * candidate that isn't on this server's own origin AND under the * framework's `/_agent-native/` namespace. Returns the validated URI on * success, or `undefined` on rejection — callers must treat `undefined` * as a 400. * * The intentional shape is exact-prefix: * - Origin must equal the resolved request origin — no Host-header injection * reusing somebody else's registered redirect URI. Callers with a * separately verified origin may pass it as `expectedOrigin`. * - Path must start with `${appBasePath}/_agent-native/` so we never * hand auth codes to a public marketing or open-redirect endpoint * on the same registered host. * * For desktop / native flows that need ephemeral `http://127.0.0.1:` * loopback URIs, callers should validate those at the template level * with a dedicated allowlist — this helper rejects them by design. */ export declare function isAllowedOAuthRedirectUri(candidate: string, event: H3Event, expectedOrigin?: string, options?: OAuthRedirectUriOptions): boolean; export declare function resolveOAuthRedirectUri(event: H3Event, defaultPath?: string, options?: OAuthRedirectUriOptions): string | null; export interface OAuthStatePayload { redirectUri: string; owner?: string; orgId?: string; desktop?: boolean; mobile?: boolean; addAccount?: boolean; app?: string; scope?: string; provider?: string; returnUrl?: string; flowId?: string; oauthTargetId?: string; desktopVerifierHash?: string; desktopBrowserBindingHash?: string; desktopWebview?: boolean; signupAttribution?: Record; signupAnonymousId?: string; signupSessionId?: string; mcpSignupAttribution?: McpSignupAttribution; } /** * Derive a server-only signing key for HMAC verification of OAuth state. * * Uses a dedicated secret — never an OAuth client secret. Reusing a * client_secret (which is shared with Google / GitHub / Atlassian) as our * own HMAC key conflates two trust domains: rotating the client secret * silently invalidates every in-flight OAuth state, and any leak of the * client secret also lets an attacker forge our state envelopes. * * Resolution order: * 1. OAUTH_STATE_SECRET (preferred — dedicated to this purpose) * 2. BETTER_AUTH_SECRET (already used by Better Auth as a server secret) * 3. Hosted workspace deploys derive a per-purpose key from A2A_SECRET * 4. In dev only, an ephemeral random key (per-process) * * In production, throws if no usable server secret is set. */ export declare function getOAuthStateSigningKey(): string; export interface EncodeOAuthStateOptions { redirectUri: string; owner?: string; orgId?: string; desktop?: boolean; mobile?: boolean; addAccount?: boolean; app?: string; scope?: string; provider?: string; returnUrl?: string; flowId?: string; oauthTargetId?: string; desktopVerifierHash?: string; desktopBrowserBindingHash?: string; desktopWebview?: boolean; signupAttribution?: Record; signupAnonymousId?: string; signupSessionId?: string; mcpSignupAttribution?: McpSignupAttribution; } export declare function encodeOAuthState(opts: EncodeOAuthStateOptions): string; export declare function encodeOAuthState(redirectUri: string, owner?: string, desktop?: boolean, addAccount?: boolean, app?: string, returnUrl?: string, flowId?: string): string; export type OAuthStateDecodeFailureReason = "missing-state" | "missing-delimiter" | "bad-signature" | "malformed-payload"; export type DecodeOAuthStateResult = ({ ok: true; } & OAuthStatePayload) | { ok: false; reason: OAuthStateDecodeFailureReason; redirectUri: string; }; export declare function decodeOAuthState(stateParam: string | undefined, fallbackUri: string): DecodeOAuthStateResult; export declare function logOAuthStateDecodeFailure(event: H3Event, reason: OAuthStateDecodeFailureReason, provider?: string): void; export interface OAuthOwnerResult { owner: string | undefined; hasProductionSession: boolean; } export declare function resolveOAuthOwner(event: H3Event, stateOwner?: string): Promise; export interface OAuthSessionResult { sessionToken: string | undefined; } export declare function createOAuthSession(event: H3Event, email: string, opts: { hasProductionSession: boolean; desktop?: boolean; mobile?: boolean; authProvider?: "google" | `sso:${string}` | null; trackSignup?: { authProvider: string; authUserId?: string; canonicalAuthUserId?: string; name?: string | null; attribution?: Record; signupAnonymousId?: string; signupSessionId?: string; mcpSignupAttribution?: McpSignupAttribution; isNewUser?: boolean; }; }): Promise; export declare function oauthCallbackResponse(event: H3Event, email: string, opts: { sessionToken?: string; desktop?: boolean; mobile?: boolean; addAccount?: boolean; returnUrl?: string; flowId?: string; appName?: string; desktopWebview?: boolean; }): unknown; export declare function oauthErrorPage(message: string, status?: number, options?: { backHref?: string; backLabel?: string; }): Response; export declare function oauthDesktopExchangePage(message?: string, closeWindow?: boolean): Response;