export interface HmacAttempt {
    ts: string;
    nonce: string;
    sig: string;
}
/**
 * Canonical "binding" string mixed into the signed payload so a captured HMAC
 * header can't be replayed against a different route or port. Without it, a
 * single set of `X-Botmux-Cli-*` headers signed only over `ts:nonce` is valid
 * for ANY `/__cli/*` route on ANY dashboard — which lets a malicious local
 * server, handed a discovery probe, forward the headers to the real dashboard
 * (e.g. a `/__cli/current` probe relayed to `/__cli/rotate` to mint a token).
 * Binding `method + path + bound-port` makes the credential single-purpose:
 *  - the verifier uses the port IT actually bound (not the attacker-controlled
 *    Host header), so a forward from port X to the dashboard on port Y mismatches;
 *  - a `/__cli/current` capture can't be replayed to `/__cli/rotate` (path differs).
 */
export declare function cliAuthBind(method: string, path: string, port: number | string): string;
/** Mint the three `X-Botmux-Cli-*` header values for a loopback request.
 *  Pass the same `bind` (see {@link cliAuthBind}) the verifier will reconstruct;
 *  omit it only for the legacy daemon-IPC scheme that signs bare `ts:nonce`. */
export declare function signCliAuth(secretB64Url: string, bind?: string): HmacAttempt;
/**
 * Verify a CLI rotation HMAC attempt.
 * - Source IP must be loopback (127.0.0.1 / ::1 / IPv4-mapped form).
 * - Timestamp must be within ±TS_WINDOW_S seconds of now.
 * - Nonce must not have been seen in the last NONCE_TTL_MS.
 * - HMAC-SHA256(secret, `${ts}:${nonce}` [+ `:${bind}`]) must match `sig`
 *   (timing-safe). Pass `bind` (method+path+port) to scope the credential to a
 *   single route/port; omit it for the legacy bare daemon-IPC scheme.
 */
export declare function verifyHmac(secretB64Url: string, attempt: HmacAttempt, remoteAddr: string, bind?: string): {
    ok: boolean;
    reason?: string;
};
/** 32 random bytes base64url-encoded (43 characters, no padding). */
export declare function generateToken(): string;
/**
 * Load the persisted active dashboard token from `tokenPath`, or `null` when
 * the file is absent / empty / unreadable.
 *
 * Persisting the active token lets a previously-issued dashboard URL survive a
 * `botmux restart`: on startup the dashboard hydrates `activeToken` from this
 * file instead of starting blank.  Only `botmux dashboard` rotates the token
 * (via `persistToken` with a fresh value), which is what invalidates the old
 * link.
 */
export declare function loadPersistedToken(tokenPath: string): string | null;
/** Persist the active dashboard token to `tokenPath` with 0600 perms. */
export declare function persistToken(tokenPath: string, token: string): void;
/** Extract `botmux_dashboard_token` value from a Cookie header. */
export declare function parseCookie(header: string | undefined): string | undefined;
/** Build the `Set-Cookie` header value for a fresh dashboard token. */
export declare function buildSetCookie(token: string): string;
/**
 * The dashboard splits incoming requests into three categories before the
 * route handlers run:
 *
 *   - `allow`            — request can proceed (auth succeeded OR endpoint
 *                          is public)
 *   - `allow+set-cookie` — `?t=<correct-token>` query: the cookie is set
 *                          and we redirect to a clean URL.  This is the
 *                          only branch that mints a Set-Cookie header.
 *   - `deny401`          — endpoint requires an authenticated session and
 *                          none was presented.
 *
 * Public surfaces today (codex review v0.1.2 → canary.3):
 *   - `GET /` and `GET /assets/*`              — static SPA shell
 *   - `GET /api/workflows/*`                   — workflow read-only API,
 *                                                EXCEPT `…/terminal-log/raw`
 *                                                which serves full PTY byte
 *                                                streams (may include keys,
 *                                                env, tokens) and requires
 *                                                cookie auth.
 *
 * Anything else (sessions, schedules, dashboard rotate, POST /api/workflows
 * /…/cancel, etc.) requires the active session token, matching the
 * "get_write_link" pattern that the chat web terminal already uses.
 */
export type AuthDecision = {
    kind: 'allow';
} | {
    kind: 'allow+set-cookie';
    token: string;
    redirectTo: string;
} | {
    kind: 'deny401';
};
export declare function decideDashboardAuth(opts: {
    method: string;
    pathname: string;
    hasTokenParam: boolean;
    presentedToken: string | undefined;
    activeToken: string;
    /** When true (config.dashboard.publicReadOnly), the GET/HEAD paths in
     *  PUBLIC_READ_PATHS (the "watch work" board surfaces) are readable WITHOUT
     *  a token — a tokenless (or stale-token) visitor gets a read-only dashboard
     *  instead of a 401 wall. Everything else (management/config reads, raw PTY,
     *  all writes) still requires the active token. */
    publicReadOnly?: boolean;
}): AuthDecision;
//# sourceMappingURL=auth.d.ts.map