/**
 * auth-remote — HTTP helper for `olam auth --remote <url>` subcommands.
 *
 * Wraps fetch calls against the auth-worker's `/v1/*` endpoints.
 *
 * V1 dogfood note: CF Access cookie must be obtained manually via browser.
 * A proper CLI-orchestrated SSO flow (CLI-as-OAuth-client) is deferred to v2.
 * See e4/e5/e6 task notes in docs/plans/auth-worker-multitenant-vault/.
 */
function normalizeBase(url) {
    return url.replace(/\/+$/, '');
}
/**
 * Build common headers for auth-worker requests.
 * When cfAuthCookie is provided, forwards it so the worker's CF Access
 * middleware can authenticate the request server-side.
 */
function buildHeaders(cookie, cfAccess) {
    const headers = {
        'Content-Type': 'application/json',
        Accept: 'application/json',
    };
    // Phase H h2: prefer machine-to-machine service-token headers when present.
    // The CF Access edge validates these and injects the SSO JWT the worker's
    // resolveIdentity expects. Cookie + service token are not mutually
    // exclusive — both may be sent; the edge honours the service token.
    if (cfAccess) {
        headers['CF-Access-Client-Id'] = cfAccess.clientId;
        headers['CF-Access-Client-Secret'] = cfAccess.clientSecret;
    }
    if (cookie) {
        headers['Cookie'] = `CF_Authorization=${cookie}`;
    }
    return headers;
}
/** Extract the CF Access service-token pair from options, or undefined. */
function cfAccessFromOpts(opts) {
    if (opts.cfAccessClientId && opts.cfAccessClientSecret) {
        return { clientId: opts.cfAccessClientId, clientSecret: opts.cfAccessClientSecret };
    }
    return undefined;
}
/**
 * GET /v1/health — worker liveness probe.
 */
export async function remoteHealth(opts) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/health`;
    const res = await fetchFn(url, {
        method: 'GET',
        headers: buildHeaders(cfAuthCookie),
    });
    if (!res.ok) {
        throw new Error(`Health check failed: HTTP ${res.status}`);
    }
    return res.json();
}
/**
 * POST /v1/oauth/start — begin an OAuth flow.
 * Returns { authorize_url, state_id }.
 */
export async function remoteOAuthStart(opts) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/oauth/start`;
    const res = await fetchFn(url, {
        method: 'POST',
        headers: buildHeaders(cfAuthCookie),
        body: JSON.stringify({}),
    });
    if (!res.ok) {
        const body = await res.text().catch(() => '');
        throw new Error(`OAuth start failed: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
    }
    return res.json();
}
/**
 * GET /v1/service-tokens — list bound service tokens.
 */
export async function remoteListServiceTokens(opts) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/service-tokens`;
    const res = await fetchFn(url, {
        method: 'GET',
        headers: buildHeaders(cfAuthCookie),
    });
    if (!res.ok) {
        throw new Error(`List service-tokens failed: HTTP ${res.status}`);
    }
    return res.json();
}
/**
 * GET /v1/accounts — list registered accounts (Anthropic OAuth sessions).
 */
export async function remoteListAccounts(opts) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/accounts`;
    const res = await fetchFn(url, {
        method: 'GET',
        headers: buildHeaders(cfAuthCookie),
    });
    if (!res.ok) {
        throw new Error(`List accounts failed: HTTP ${res.status}`);
    }
    return res.json();
}
/**
 * POST /v1/service-tokens/bind — bind a CF service token to a user sub.
 */
export async function remoteBindServiceToken(opts, payload) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/service-tokens/bind`;
    const res = await fetchFn(url, {
        method: 'POST',
        headers: buildHeaders(cfAuthCookie),
        body: JSON.stringify(payload),
    });
    if (!res.ok) {
        const body = await res.text().catch(() => '');
        throw new Error(`Bind service-token failed: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
    }
    return { ok: true };
}
/**
 * DELETE /v1/service-tokens/:client_id — revoke a service token.
 */
export async function remoteDeleteServiceToken(opts, clientId) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/service-tokens/${encodeURIComponent(clientId)}`;
    const res = await fetchFn(url, {
        method: 'DELETE',
        headers: buildHeaders(cfAuthCookie),
    });
    if (!res.ok) {
        throw new Error(`Delete service-token failed: HTTP ${res.status}`);
    }
    return { ok: true };
}
// ── g4: Anthropic proxy token helpers ────────────────────────────────────────
// Types AnthropicTokenIssueResponse + AnthropicTokenEntry are declared at the
// top of this file (lines 35-48) — added by PR #972 which independently fixed
// the JSDoc-eating-types bug while this branch was in flight. The cherry-pick
// of commit 19512c47 (which removed the stray /** that was hiding the
// duplicate declarations below) exposed both copies; the duplicate block is
// removed here to fix the resulting TS2300 "Duplicate identifier" error.
/**
 * POST /v1/anthropic-tokens/issue — mint a new Anthropic proxy token.
 * Returns { secret, token_hash, label, anthropic_base_url_hint }.
 */
export async function remoteIssueAnthropicToken(opts, label) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/anthropic-tokens/issue`;
    const res = await fetchFn(url, {
        method: 'POST',
        headers: buildHeaders(cfAuthCookie, cfAccessFromOpts(opts)),
        body: JSON.stringify({ label }),
    });
    if (!res.ok) {
        const body = await res.text().catch(() => '');
        throw new Error(`Issue anthropic-token failed: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
    }
    return res.json();
}
/**
 * GET /v1/anthropic-tokens — list issued Anthropic proxy tokens.
 */
export async function remoteListAnthropicTokens(opts) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/anthropic-tokens`;
    const res = await fetchFn(url, {
        method: 'GET',
        headers: buildHeaders(cfAuthCookie, cfAccessFromOpts(opts)),
    });
    if (!res.ok) {
        const body = await res.text().catch(() => '');
        throw new Error(`List anthropic-tokens failed: HTTP ${res.status}${body ? ` — ${body}` : ''}`);
    }
    return res.json();
}
/**
 * DELETE /v1/anthropic-tokens/:token_hash — revoke an Anthropic proxy token.
 * Returns true on 200/204, false on 404.
 */
export async function remoteRevokeAnthropicToken(opts, tokenHash) {
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const url = `${normalizeBase(baseUrl)}/v1/anthropic-tokens/${encodeURIComponent(tokenHash)}`;
    const res = await fetchFn(url, {
        method: 'DELETE',
        headers: buildHeaders(cfAuthCookie, cfAccessFromOpts(opts)),
    });
    if (res.status === 404)
        return false;
    if (!res.ok) {
        throw new Error(`Revoke anthropic-token failed: HTTP ${res.status}`);
    }
    return true;
}
/**
 * runDoctorChecks — runs 4 diagnostic probes against the auth-worker.
 * Returns an ordered array of DoctorCheckResult (pass/fail/warn + remedy).
 *
 * Probe 1: GET /v1/health → liveness
 * Probe 2: CF Access JWKS reachable (derives team domain from worker URL)
 * Probe 3: POST /v1/oauth/start → endpoint reachability
 * Probe 4: ANTHROPIC_OAUTH_CLIENT_ID env present (config check)
 */
export async function runDoctorChecks(opts) {
    const results = [];
    const { baseUrl, cfAuthCookie, fetchFn = fetch } = opts;
    const base = normalizeBase(baseUrl);
    // Probe 1: /v1/health
    try {
        const h = await remoteHealth({ baseUrl, cfAuthCookie, fetchFn });
        results.push({
            probe: 'worker-health',
            status: 'pass',
            message: `Worker alive${h.version ? ` (version=${h.version})` : ''}`,
        });
    }
    catch (err) {
        results.push({
            probe: 'worker-health',
            status: 'fail',
            message: err instanceof Error ? err.message : 'Health check failed',
            remedy: `Verify the worker is deployed and reachable at ${base}`,
        });
    }
    // Probe 2: CF Access JWKS
    // Extract team domain from the worker URL host segment.
    // e.g. https://auth.example.workers.dev → team = "example"
    // For custom domains like auth.myteam.example.com, we look for the last
    // two segments of the authority. In practice workers.dev domains expose team name.
    let jwksUrl;
    try {
        const parsed = new URL(baseUrl);
        // heuristic: take the second-to-last part of the hostname before TLD pair
        const parts = parsed.hostname.split('.');
        // e.g. auth.myteam.cloudflareaccess.com or myteam.cloudflareaccess.com
        // Best-effort: use the host as the team domain search key
        const team = parts.length >= 3 ? parts[parts.length - 3] : parts[0];
        jwksUrl = `https://${team}.cloudflareaccess.com/cdn-cgi/access/certs`;
    }
    catch {
        jwksUrl = `${base}/.well-known/jwks.json`;
    }
    try {
        const res = await fetchFn(jwksUrl, { method: 'GET' });
        if (res.ok) {
            results.push({
                probe: 'cf-access-jwks',
                status: 'pass',
                message: `CF Access JWKS reachable at ${jwksUrl}`,
            });
        }
        else {
            results.push({
                probe: 'cf-access-jwks',
                status: 'warn',
                message: `CF Access JWKS returned HTTP ${res.status}`,
                remedy: `Verify Cloudflare Access is configured for your zone. JWKS checked: ${jwksUrl}`,
            });
        }
    }
    catch (err) {
        results.push({
            probe: 'cf-access-jwks',
            status: 'warn',
            message: `CF Access JWKS unreachable: ${err instanceof Error ? err.message : String(err)}`,
            remedy: `Check network access to ${jwksUrl}. CF Access may not be configured for this zone.`,
        });
    }
    // Probe 3: POST /v1/oauth/start
    try {
        await remoteOAuthStart({ baseUrl, cfAuthCookie, fetchFn });
        results.push({
            probe: 'oauth-start',
            status: 'pass',
            message: 'OAuth start endpoint is reachable',
        });
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        // 403 means CF Access is enforcing SSO but the endpoint exists — that's a warn.
        const status = msg.includes('HTTP 403') ? 'warn' : 'fail';
        results.push({
            probe: 'oauth-start',
            status,
            message: `OAuth start: ${msg}`,
            remedy: status === 'warn'
                ? 'Open the worker URL in a browser to complete CF Access SSO, then re-run with --cookie.'
                : `Check that /v1/oauth/start is deployed at ${base}`,
        });
    }
    // Probe 4: ANTHROPIC_OAUTH_CLIENT_ID env
    const clientId = process.env['ANTHROPIC_OAUTH_CLIENT_ID'];
    if (clientId) {
        results.push({
            probe: 'env-client-id',
            status: 'pass',
            message: `ANTHROPIC_OAUTH_CLIENT_ID is set (length=${clientId.length})`,
        });
    }
    else {
        results.push({
            probe: 'env-client-id',
            status: 'warn',
            message: 'ANTHROPIC_OAUTH_CLIENT_ID is not set in the environment',
            remedy: 'Set ANTHROPIC_OAUTH_CLIENT_ID in your shell profile (the Anthropic OAuth app client ID).',
        });
    }
    return results;
}
//# sourceMappingURL=auth-remote.js.map