#!/usr/bin/env node /** * Explicit live authorization check against an already-running Chickpea Node * or Cloudflare target. It never runs implicitly, never sends a model prompt, * and keeps the browser attempt capability in process memory only. * * The direct originator/Responses compatibility gate is intentionally separate: * npm run verify:openai-subscription-protocol -- --live */ import assert from 'node:assert/strict'; const args = new Map(); for (let index = 2; index < process.argv.length; index += 1) { const value = process.argv[index]; if (!value?.startsWith('--')) continue; const next = process.argv[index + 1]; if (next && !next.startsWith('--')) { args.set(value, next); index += 1; } else { args.set(value, true); } } if (args.has('--help')) { console.log('Usage: npm run verify:openai-subscription:live -- --live --target --base-url '); console.log('Requires CHICKPEA_PERSONAL_TOKEN. This validates authorization only; it does not send model traffic or disconnect the account.'); process.exit(0); } assert.equal( args.has('--live'), true, 'refusing external authorization without the explicit --live flag', ); const target = args.get('--target'); assert.ok(target === 'node' || target === 'cloudflare', '--target must be node or cloudflare'); const baseUrl = new URL(String(args.get('--base-url') ?? '')); assert.equal(baseUrl.username || baseUrl.password, '', 'base URL must not contain credentials'); if (target === 'cloudflare') assert.equal(baseUrl.protocol, 'https:', 'Cloudflare target must use HTTPS'); else assert.ok( baseUrl.protocol === 'https:' || (baseUrl.protocol === 'http:' && isLoopbackHostname(baseUrl.hostname)), 'Node target must use HTTPS unless it is an explicit loopback host', ); const personalToken = process.env.CHICKPEA_PERSONAL_TOKEN; assert.ok(personalToken, 'CHICKPEA_PERSONAL_TOKEN is required and is never printed'); const headers = { authorization: `Bearer ${personalToken}`, 'content-type': 'application/json', }; const endpoint = (path) => new URL(path, baseUrl).href; const request = async (path, init = {}) => { const response = await fetch(endpoint(path), { redirect: 'error', ...init, headers: { ...headers, ...(init.headers ?? {}) }, }); const body = await response.json().catch(() => ({})); if (!response.ok) { const code = typeof body.error === 'string' ? body.error : `http_${response.status}`; throw new Error(`live authorization check failed (${code})`); } return body; }; const before = await request('/admin/api/providers/openai/subscription'); if (before.status?.state === 'connected') { console.log(`[openai-subscription] ${target} target already connected (${before.status.accountFingerprint ?? 'safe identity unavailable'}).`); console.log('[openai-subscription] Authorization is available; run the separate Slack/routine acceptance checklist without exposing credentials.'); process.exit(0); } const started = await request('/admin/api/providers/openai/subscription/start', { method: 'POST', body: '{}', }); assert.equal(started.state, 'authorizing'); assert.equal(typeof started.attemptCapability, 'string'); console.log(`[openai-subscription] Open ${started.verificationUri}`); console.log(`[openai-subscription] Enter code: ${started.userCode}`); console.log(`[openai-subscription] Waiting on the ${target} target; the browser capability remains memory-only.`); let nextPollAt = Number(started.nextPollAt ?? Date.now()); while (Date.now() < Number(started.expiresAt)) { await new Promise((resolve) => setTimeout(resolve, Math.max(nextPollAt - Date.now(), 1_000))); const result = await request('/admin/api/providers/openai/subscription/poll', { method: 'POST', body: JSON.stringify({ attemptCapability: started.attemptCapability }), }); if (result.state === 'pending') { nextPollAt = Number(result.nextPollAt ?? (Date.now() + 5_000)); continue; } assert.equal(result.state, 'connected', `unexpected terminal state ${String(result.state)}`); console.log(`[openai-subscription] ${target} authorization verified (${result.accountFingerprint ?? 'safe identity unavailable'}).`); console.log('[openai-subscription] This did not send a model request. Complete the Slack, routine, no-fallback, and refresh gates in the runbook.'); process.exit(0); } throw new Error('authorization expired before approval'); function isLoopbackHostname(hostname) { return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]'; }