@open-vibe-lab/open-sub-auth 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/{claude-3WKImhqM.mjs → claude-Bu4Vb3Sa.mjs} +18 -4
- package/dist/cli/{claude-3WKImhqM.mjs.map → claude-Bu4Vb3Sa.mjs.map} +1 -1
- package/dist/cli/{registry-Cp-_Ipc6.mjs → errors-BQeS4Q64.mjs} +11 -37
- package/dist/cli/errors-BQeS4Q64.mjs.map +1 -0
- package/dist/cli/export-CpcD-XlM.mjs +11 -0
- package/dist/cli/export-CpcD-XlM.mjs.map +1 -0
- package/dist/cli/import-BZDRd0Nj.mjs +40 -0
- package/dist/cli/import-BZDRd0Nj.mjs.map +1 -0
- package/dist/cli/index.mjs +61 -12
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/{login-_HdTW5J_.mjs → login-DfG9kqBN.mjs} +7 -6
- package/dist/cli/login-DfG9kqBN.mjs.map +1 -0
- package/dist/cli/{logout-CZm-tSVH.mjs → logout-BdnhQ3aw.mjs} +7 -6
- package/dist/cli/logout-BdnhQ3aw.mjs.map +1 -0
- package/dist/cli/manager-C-RjavF0.mjs +129 -0
- package/dist/cli/manager-C-RjavF0.mjs.map +1 -0
- package/dist/cli/{oauth-pkce-Bi02-h23.mjs → oauth-pkce-CCKyG5wP.mjs} +4 -10
- package/dist/cli/{oauth-pkce-Bi02-h23.mjs.map → oauth-pkce-CCKyG5wP.mjs.map} +1 -1
- package/dist/cli/{openai-codex-DJi_Q6Zm.mjs → openai-codex-CKKY1Gu_.mjs} +3 -3
- package/dist/cli/{openai-codex-DJi_Q6Zm.mjs.map → openai-codex-CKKY1Gu_.mjs.map} +1 -1
- package/dist/cli/registry-Dv4TWuqv.mjs +39 -0
- package/dist/cli/registry-Dv4TWuqv.mjs.map +1 -0
- package/dist/cli/{status-C-vkcjVM.mjs → status-CvktIWmx.mjs} +6 -5
- package/dist/cli/status-CvktIWmx.mjs.map +1 -0
- package/dist/cli/{manager-CKGbp7Yz.mjs → store-57VEqlSz.mjs} +16 -132
- package/dist/cli/store-57VEqlSz.mjs.map +1 -0
- package/dist/cli/{token-DF_-h4Rb.mjs → token-Ce_FmqGM.mjs} +7 -6
- package/dist/cli/token-Ce_FmqGM.mjs.map +1 -0
- package/dist/index.cjs +271 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -4
- package/dist/index.d.mts +67 -4
- package/dist/index.mjs +265 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/cli/login-_HdTW5J_.mjs.map +0 -1
- package/dist/cli/logout-CZm-tSVH.mjs.map +0 -1
- package/dist/cli/manager-CKGbp7Yz.mjs.map +0 -1
- package/dist/cli/registry-Cp-_Ipc6.mjs.map +0 -1
- package/dist/cli/status-C-vkcjVM.mjs.map +0 -1
- package/dist/cli/token-DF_-h4Rb.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-pkce-Bi02-h23.mjs","names":[],"sources":["../../src/core/browser.ts","../../src/core/callback-server.ts","../../src/core/crypto.ts","../../src/core/manual-code-input.ts","../../src/core/oauth-pkce.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { platform } from \"node:os\";\n\n/** Open a URL in the user's default browser. Does not throw on failure. */\nexport function openBrowser(url: string): void {\n const os = platform();\n try {\n if (os === \"darwin\") {\n execFile(\"open\", [url]);\n } else if (os === \"win32\") {\n execFile(\"cmd\", [\"/c\", \"start\", \"\", url]);\n } else {\n execFile(\"xdg-open\", [url]);\n }\n } catch {\n // Silently ignore — caller should provide fallback instructions\n }\n}\n","import { createServer, type Server } from \"node:http\";\nimport { OAuthCallbackError, OAuthTimeoutError } from \"@/errors.ts\";\nimport type { AuthorizationResult } from \"@/types.ts\";\n\nconst SUCCESS_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Authorization Successful</title>\n<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f8f9fa}\n.card{text-align:center;padding:2rem;border-radius:12px;background:white;box-shadow:0 2px 8px rgba(0,0,0,0.1)}\nh1{color:#22c55e;font-size:1.5rem}p{color:#666}</style></head>\n<body><div class=\"card\"><h1>Authorization Successful</h1><p>You can close this window and return to the terminal.</p></div></body></html>`;\n\nconst ERROR_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Authorization Failed</title>\n<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f8f9fa}\n.card{text-align:center;padding:2rem;border-radius:12px;background:white;box-shadow:0 2px 8px rgba(0,0,0,0.1)}\nh1{color:#ef4444;font-size:1.5rem}p{color:#666}</style></head>\n<body><div class=\"card\"><h1>Authorization Failed</h1><p>Something went wrong. Please try again.</p></div></body></html>`;\n\nexport interface CallbackServerOptions {\n /** Port to listen on (0 = random available port) */\n port?: number;\n /** Expected state parameter for CSRF validation */\n expectedState: string;\n /** Timeout in milliseconds (default: 120000) */\n timeout?: number;\n /** Path to listen on (default: \"/callback\") */\n path?: string;\n}\n\n/** Start a local HTTP server to receive the OAuth callback redirect */\nexport function startCallbackServer(\n options: CallbackServerOptions,\n): Promise<{ result: Promise<AuthorizationResult>; port: number; close: () => void }> {\n const { expectedState, timeout = 120_000, path = \"/callback\" } = options;\n const port = options.port ?? 0;\n\n return new Promise((resolveSetup, _rejectSetup) => {\n let server: Server;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const resultPromise = new Promise<AuthorizationResult>((resolveResult, rejectResult) => {\n server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://localhost`);\n\n if (url.pathname !== path) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n const errorDesc = url.searchParams.get(\"error_description\") ?? error;\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(new OAuthCallbackError(`OAuth error: ${errorDesc}`));\n return;\n }\n\n if (!code) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(new OAuthCallbackError(\"No authorization code in callback\"));\n return;\n }\n\n if (state && state !== expectedState) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(\n new OAuthCallbackError(\"State mismatch — possible CSRF attack. Please try again.\"),\n );\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(SUCCESS_HTML);\n cleanup();\n resolveResult({ code, state: state ?? expectedState });\n });\n\n const cleanup = () => {\n if (timeoutId) clearTimeout(timeoutId);\n server.close();\n };\n\n timeoutId = setTimeout(() => {\n cleanup();\n rejectResult(new OAuthTimeoutError(timeout));\n }, timeout);\n\n server.on(\"error\", (err) => {\n cleanup();\n rejectResult(new OAuthCallbackError(`Callback server error: ${err.message}`));\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n const addr = server.address();\n const actualPort = typeof addr === \"object\" && addr ? addr.port : port;\n resolveSetup({\n result: resultPromise,\n port: actualPort,\n close: cleanup,\n });\n });\n });\n });\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport type { PKCEParams } from \"@/types.ts\";\n\n/** Generate a cryptographically random PKCE code verifier (43-128 chars, base64url) */\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** Generate a PKCE code challenge from a verifier using S256 method */\nexport function generateCodeChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/** Generate both PKCE code verifier and challenge */\nexport function generatePKCE(): PKCEParams {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n return { codeVerifier, codeChallenge };\n}\n\n/** Generate a random state parameter for CSRF protection */\nexport function generateState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n","import { createInterface } from \"node:readline\";\nimport { OAuthCallbackError, StateMismatchError } from \"@/errors.ts\";\nimport type { AuthorizationResult } from \"@/types.ts\";\n\n/**\n * Parse a \"code#state\" string (Anthropic's manual paste format).\n * Also supports a plain authorization code (state validated separately).\n */\nexport function parseCodeAndState(input: string, expectedState: string): AuthorizationResult {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new OAuthCallbackError(\"Empty authorization code input\");\n }\n\n const hashIndex = trimmed.indexOf(\"#\");\n if (hashIndex !== -1) {\n const code = trimmed.slice(0, hashIndex);\n const state = trimmed.slice(hashIndex + 1);\n if (!code) {\n throw new OAuthCallbackError(\"Empty authorization code in code#state input\");\n }\n if (state !== expectedState) {\n throw new StateMismatchError();\n }\n return { code, state };\n }\n\n // No \"#\" found — assume just the code was provided\n return { code: trimmed, state: expectedState };\n}\n\n/** Prompt the user to paste the authorization code from their browser */\nexport function promptForCode(expectedState: string): Promise<AuthorizationResult> {\n return new Promise((resolve, reject) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stderr, // Use stderr so stdout stays clean for piping\n });\n\n rl.question(\"Paste the authorization code (or code#state) from your browser: \", (answer) => {\n rl.close();\n try {\n resolve(parseCodeAndState(answer, expectedState));\n } catch (err) {\n reject(err);\n }\n });\n });\n}\n","import { OAuthCallbackError } from \"@/errors.ts\";\nimport type { AuthorizationResult, LoginOptions, ProviderConfig, TokenSet } from \"@/types.ts\";\nimport { openBrowser } from \"@/core/browser.ts\";\nimport { startCallbackServer } from \"@/core/callback-server.ts\";\nimport { generatePKCE, generateState } from \"@/core/crypto.ts\";\nimport { promptForCode } from \"@/core/manual-code-input.ts\";\n\n/** Build the full OAuth authorization URL with PKCE and state params */\nexport function buildAuthorizationUrl(\n config: ProviderConfig,\n codeChallenge: string,\n state: string,\n redirectUri: string,\n): string {\n if (!config.authorizationEndpoint) {\n throw new OAuthCallbackError(`Provider \"${config.name}\" has no authorization endpoint`);\n }\n\n const url = new URL(config.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", config.clientId);\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\"code_challenge\", codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"state\", state);\n\n if (config.scopes?.length) {\n url.searchParams.set(\"scope\", config.scopes.join(\" \"));\n }\n\n return url.toString();\n}\n\n/** Exchange an authorization code for tokens */\nexport async function exchangeCode(\n config: ProviderConfig,\n code: string,\n codeVerifier: string,\n redirectUri: string,\n state?: string,\n): Promise<TokenSet> {\n const useJson = config.tokenBodyFormat === \"json\";\n\n const params: Record<string, string> = {\n grant_type: \"authorization_code\",\n code,\n code_verifier: codeVerifier,\n client_id: config.clientId,\n redirect_uri: redirectUri,\n };\n if (state !== undefined) {\n params.state = state;\n }\n\n const response = await fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": useJson ? \"application/json\" : \"application/x-www-form-urlencoded\" },\n body: useJson ? JSON.stringify(params) : new URLSearchParams(params).toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new OAuthCallbackError(`Token exchange failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return parseTokenResponse(data);\n}\n\n/** Refresh an access token using a refresh token */\nexport async function refreshAccessToken(\n config: ProviderConfig,\n refreshToken: string,\n): Promise<TokenSet> {\n const useJson = config.tokenBodyFormat === \"json\";\n\n const params: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientId,\n };\n\n const response = await fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": useJson ? \"application/json\" : \"application/x-www-form-urlencoded\" },\n body: useJson ? JSON.stringify(params) : new URLSearchParams(params).toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new OAuthCallbackError(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return parseTokenResponse(data);\n}\n\nexport interface PKCEFlowOptions {\n config: ProviderConfig;\n loginOptions?: LoginOptions;\n /**\n * Manual mode redirect URI (used when manual=true).\n * If not provided, manual mode is not available.\n */\n manualRedirectUri?: string;\n}\n\n/** Execute the full PKCE flow: generate params, open browser, wait for callback, exchange code */\nexport async function executePKCEFlow(options: PKCEFlowOptions): Promise<TokenSet> {\n const { config, loginOptions } = options;\n const { codeVerifier, codeChallenge } = generatePKCE();\n // Some providers (e.g. Claude) require state === verifier for their token exchange\n const state = config.stateIsVerifier ? codeVerifier : generateState();\n const manual = loginOptions?.manual ?? false;\n\n let authResult: AuthorizationResult;\n let redirectUri: string;\n\n if (manual && options.manualRedirectUri) {\n // Manual mode: redirect to provider's callback page, user copies code\n redirectUri = options.manualRedirectUri;\n const authUrl = buildAuthorizationUrl(config, codeChallenge, state, redirectUri);\n\n if (loginOptions?.onOpenBrowser) {\n loginOptions.onOpenBrowser(authUrl);\n } else {\n openBrowser(authUrl);\n }\n\n process.stderr.write(\n `\\nOpen this URL in your browser if it didn't open automatically:\\n${authUrl}\\n\\n`,\n );\n authResult = await promptForCode(state);\n } else {\n // Automatic mode: local callback server\n const port = loginOptions?.port ?? 0;\n const timeout = loginOptions?.timeout ?? 120_000;\n\n const server = await startCallbackServer({\n port,\n expectedState: state,\n timeout,\n });\n\n redirectUri = `http://127.0.0.1:${server.port}/callback`;\n const authUrl = buildAuthorizationUrl(config, codeChallenge, state, redirectUri);\n\n if (loginOptions?.onOpenBrowser) {\n loginOptions.onOpenBrowser(authUrl);\n } else {\n openBrowser(authUrl);\n }\n\n process.stderr.write(\n `\\nOpen this URL in your browser if it didn't open automatically:\\n${authUrl}\\n\\nWaiting for authorization...\\n`,\n );\n\n try {\n authResult = await server.result;\n } catch (err) {\n server.close();\n throw err;\n }\n }\n\n // Pass state to exchange when provider uses JSON body format (e.g. Claude)\n const exchangeState = config.tokenBodyFormat === \"json\" ? authResult.state : undefined;\n return exchangeCode(config, authResult.code, codeVerifier, redirectUri, exchangeState);\n}\n\nfunction parseTokenResponse(data: Record<string, unknown>): TokenSet {\n const accessToken = data.access_token as string | undefined;\n if (!accessToken) {\n throw new OAuthCallbackError(\"No access_token in token response\");\n }\n\n const expiresIn = (data.expires_in as number | undefined) ?? 3600;\n const expiresAt = Date.now() + expiresIn * 1000;\n\n return {\n accessToken,\n refreshToken: (data.refresh_token as string | undefined) ?? null,\n expiresAt,\n idToken: data.id_token as string | undefined,\n tokenType: ((data.token_type as string | undefined) ?? \"bearer\").toLowerCase() as\n | \"bearer\"\n | \"api-key\",\n scopes: data.scope ? (data.scope as string).split(\" \") : undefined,\n raw: data,\n };\n}\n"],"mappings":";;;;;;;;;AAIA,SAAgB,YAAY,KAAmB;CAC7C,MAAM,KAAK,UAAU;AACrB,KAAI;AACF,MAAI,OAAO,SACT,UAAS,QAAQ,CAAC,IAAI,CAAC;WACd,OAAO,QAChB,UAAS,OAAO;GAAC;GAAM;GAAS;GAAI;GAAI,CAAC;MAEzC,UAAS,YAAY,CAAC,IAAI,CAAC;SAEvB;;;;ACVV,MAAM,eAAe;;;;;;AAOrB,MAAM,aAAa;;;;;;;AAmBnB,SAAgB,oBACd,SACoF;CACpF,MAAM,EAAE,eAAe,UAAU,MAAS,OAAO,gBAAgB;CACjE,MAAM,OAAO,QAAQ,QAAQ;AAE7B,QAAO,IAAI,SAAS,cAAc,iBAAiB;EACjD,IAAI;EACJ,IAAI;EAEJ,MAAM,gBAAgB,IAAI,SAA8B,eAAe,iBAAiB;AACtF,YAAS,cAAc,KAAK,QAAQ;IAClC,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,mBAAmB;AAEvD,QAAI,IAAI,aAAa,MAAM;AACzB,SAAI,UAAU,IAAI;AAClB,SAAI,IAAI,YAAY;AACpB;;IAGF,MAAM,OAAO,IAAI,aAAa,IAAI,OAAO;IACzC,MAAM,QAAQ,IAAI,aAAa,IAAI,QAAQ;IAC3C,MAAM,QAAQ,IAAI,aAAa,IAAI,QAAQ;AAE3C,QAAI,OAAO;KACT,MAAM,YAAY,IAAI,aAAa,IAAI,oBAAoB,IAAI;AAC/D,SAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,SAAI,IAAI,WAAW;AACnB,cAAS;AACT,kBAAa,IAAI,mBAAmB,gBAAgB,YAAY,CAAC;AACjE;;AAGF,QAAI,CAAC,MAAM;AACT,SAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,SAAI,IAAI,WAAW;AACnB,cAAS;AACT,kBAAa,IAAI,mBAAmB,oCAAoC,CAAC;AACzE;;AAGF,QAAI,SAAS,UAAU,eAAe;AACpC,SAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,SAAI,IAAI,WAAW;AACnB,cAAS;AACT,kBACE,IAAI,mBAAmB,2DAA2D,CACnF;AACD;;AAGF,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,aAAa;AACrB,aAAS;AACT,kBAAc;KAAE;KAAM,OAAO,SAAS;KAAe,CAAC;KACtD;GAEF,MAAM,gBAAgB;AACpB,QAAI,UAAW,cAAa,UAAU;AACtC,WAAO,OAAO;;AAGhB,eAAY,iBAAiB;AAC3B,aAAS;AACT,iBAAa,IAAI,kBAAkB,QAAQ,CAAC;MAC3C,QAAQ;AAEX,UAAO,GAAG,UAAU,QAAQ;AAC1B,aAAS;AACT,iBAAa,IAAI,mBAAmB,0BAA0B,IAAI,UAAU,CAAC;KAC7E;AAEF,UAAO,OAAO,MAAM,mBAAmB;IACrC,MAAM,OAAO,OAAO,SAAS;AAE7B,iBAAa;KACX,QAAQ;KACR,MAHiB,OAAO,SAAS,YAAY,OAAO,KAAK,OAAO;KAIhE,OAAO;KACR,CAAC;KACF;IACF;GACF;;;;;AC5GJ,SAAgB,uBAA+B;AAC7C,QAAO,YAAY,GAAG,CAAC,SAAS,YAAY;;;AAI9C,SAAgB,sBAAsB,UAA0B;AAC9D,QAAO,WAAW,SAAS,CAAC,OAAO,SAAS,CAAC,OAAO,YAAY;;;AAIlE,SAAgB,eAA2B;CACzC,MAAM,eAAe,sBAAsB;AAE3C,QAAO;EAAE;EAAc,eADD,sBAAsB,aAAa;EACnB;;;AAIxC,SAAgB,gBAAwB;AACtC,QAAO,YAAY,GAAG,CAAC,SAAS,YAAY;;;;;;;;ACd9C,SAAgB,kBAAkB,OAAe,eAA4C;CAC3F,MAAM,UAAU,MAAM,MAAM;AAC5B,KAAI,CAAC,QACH,OAAM,IAAI,mBAAmB,iCAAiC;CAGhE,MAAM,YAAY,QAAQ,QAAQ,IAAI;AACtC,KAAI,cAAc,IAAI;EACpB,MAAM,OAAO,QAAQ,MAAM,GAAG,UAAU;EACxC,MAAM,QAAQ,QAAQ,MAAM,YAAY,EAAE;AAC1C,MAAI,CAAC,KACH,OAAM,IAAI,mBAAmB,+CAA+C;AAE9E,MAAI,UAAU,cACZ,OAAM,IAAI,oBAAoB;AAEhC,SAAO;GAAE;GAAM;GAAO;;AAIxB,QAAO;EAAE,MAAM;EAAS,OAAO;EAAe;;;AAIhD,SAAgB,cAAc,eAAqD;AACjF,QAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,KAAK,gBAAgB;GACzB,OAAO,QAAQ;GACf,QAAQ,QAAQ;GACjB,CAAC;AAEF,KAAG,SAAS,qEAAqE,WAAW;AAC1F,MAAG,OAAO;AACV,OAAI;AACF,YAAQ,kBAAkB,QAAQ,cAAc,CAAC;YAC1C,KAAK;AACZ,WAAO,IAAI;;IAEb;GACF;;;;;ACvCJ,SAAgB,sBACd,QACA,eACA,OACA,aACQ;AACR,KAAI,CAAC,OAAO,sBACV,OAAM,IAAI,mBAAmB,aAAa,OAAO,KAAK,iCAAiC;CAGzF,MAAM,MAAM,IAAI,IAAI,OAAO,sBAAsB;AACjD,KAAI,aAAa,IAAI,iBAAiB,OAAO;AAC7C,KAAI,aAAa,IAAI,aAAa,OAAO,SAAS;AAClD,KAAI,aAAa,IAAI,gBAAgB,YAAY;AACjD,KAAI,aAAa,IAAI,kBAAkB,cAAc;AACrD,KAAI,aAAa,IAAI,yBAAyB,OAAO;AACrD,KAAI,aAAa,IAAI,SAAS,MAAM;AAEpC,KAAI,OAAO,QAAQ,OACjB,KAAI,aAAa,IAAI,SAAS,OAAO,OAAO,KAAK,IAAI,CAAC;AAGxD,QAAO,IAAI,UAAU;;;AAIvB,eAAsB,aACpB,QACA,MACA,cACA,aACA,OACmB;CACnB,MAAM,UAAU,OAAO,oBAAoB;CAE3C,MAAM,SAAiC;EACrC,YAAY;EACZ;EACA,eAAe;EACf,WAAW,OAAO;EAClB,cAAc;EACf;AACD,KAAI,UAAU,KAAA,EACZ,QAAO,QAAQ;CAGjB,MAAM,WAAW,MAAM,MAAM,OAAO,eAAe;EACjD,QAAQ;EACR,SAAS,EAAE,gBAAgB,UAAU,qBAAqB,qCAAqC;EAC/F,MAAM,UAAU,KAAK,UAAU,OAAO,GAAG,IAAI,gBAAgB,OAAO,CAAC,UAAU;EAChF,CAAC;AAEF,KAAI,CAAC,SAAS,IAAI;EAChB,MAAM,YAAY,MAAM,SAAS,MAAM;AACvC,QAAM,IAAI,mBAAmB,0BAA0B,SAAS,OAAO,KAAK,YAAY;;AAI1F,QAAO,mBADO,MAAM,SAAS,MAAM,CACJ;;;AAIjC,eAAsB,mBACpB,QACA,cACmB;CACnB,MAAM,UAAU,OAAO,oBAAoB;CAE3C,MAAM,SAAiC;EACrC,YAAY;EACZ,eAAe;EACf,WAAW,OAAO;EACnB;CAED,MAAM,WAAW,MAAM,MAAM,OAAO,eAAe;EACjD,QAAQ;EACR,SAAS,EAAE,gBAAgB,UAAU,qBAAqB,qCAAqC;EAC/F,MAAM,UAAU,KAAK,UAAU,OAAO,GAAG,IAAI,gBAAgB,OAAO,CAAC,UAAU;EAChF,CAAC;AAEF,KAAI,CAAC,SAAS,IAAI;EAChB,MAAM,YAAY,MAAM,SAAS,MAAM;AACvC,QAAM,IAAI,mBAAmB,yBAAyB,SAAS,OAAO,KAAK,YAAY;;AAIzF,QAAO,mBADO,MAAM,SAAS,MAAM,CACJ;;;AAcjC,eAAsB,gBAAgB,SAA6C;CACjF,MAAM,EAAE,QAAQ,iBAAiB;CACjC,MAAM,EAAE,cAAc,kBAAkB,cAAc;CAEtD,MAAM,QAAQ,OAAO,kBAAkB,eAAe,eAAe;CACrE,MAAM,SAAS,cAAc,UAAU;CAEvC,IAAI;CACJ,IAAI;AAEJ,KAAI,UAAU,QAAQ,mBAAmB;AAEvC,gBAAc,QAAQ;EACtB,MAAM,UAAU,sBAAsB,QAAQ,eAAe,OAAO,YAAY;AAEhF,MAAI,cAAc,cAChB,cAAa,cAAc,QAAQ;MAEnC,aAAY,QAAQ;AAGtB,UAAQ,OAAO,MACb,qEAAqE,QAAQ,MAC9E;AACD,eAAa,MAAM,cAAc,MAAM;QAClC;EAKL,MAAM,SAAS,MAAM,oBAAoB;GACvC,MAJW,cAAc,QAAQ;GAKjC,eAAe;GACf,SALc,cAAc,WAAW;GAMxC,CAAC;AAEF,gBAAc,oBAAoB,OAAO,KAAK;EAC9C,MAAM,UAAU,sBAAsB,QAAQ,eAAe,OAAO,YAAY;AAEhF,MAAI,cAAc,cAChB,cAAa,cAAc,QAAQ;MAEnC,aAAY,QAAQ;AAGtB,UAAQ,OAAO,MACb,qEAAqE,QAAQ,oCAC9E;AAED,MAAI;AACF,gBAAa,MAAM,OAAO;WACnB,KAAK;AACZ,UAAO,OAAO;AACd,SAAM;;;CAKV,MAAM,gBAAgB,OAAO,oBAAoB,SAAS,WAAW,QAAQ,KAAA;AAC7E,QAAO,aAAa,QAAQ,WAAW,MAAM,cAAc,aAAa,cAAc;;AAGxF,SAAS,mBAAmB,MAAyC;CACnE,MAAM,cAAc,KAAK;AACzB,KAAI,CAAC,YACH,OAAM,IAAI,mBAAmB,oCAAoC;CAGnE,MAAM,YAAa,KAAK,cAAqC;CAC7D,MAAM,YAAY,KAAK,KAAK,GAAG,YAAY;AAE3C,QAAO;EACL;EACA,cAAe,KAAK,iBAAwC;EAC5D;EACA,SAAS,KAAK;EACd,YAAa,KAAK,cAAqC,UAAU,aAAa;EAG9E,QAAQ,KAAK,QAAS,KAAK,MAAiB,MAAM,IAAI,GAAG,KAAA;EACzD,KAAK;EACN"}
|
|
1
|
+
{"version":3,"file":"oauth-pkce-CCKyG5wP.mjs","names":[],"sources":["../../src/core/browser.ts","../../src/core/callback-server.ts","../../src/core/crypto.ts","../../src/core/manual-code-input.ts","../../src/core/oauth-pkce.ts"],"sourcesContent":["import { execFile } from \"node:child_process\";\nimport { platform } from \"node:os\";\n\n/** Open a URL in the user's default browser. Does not throw on failure. */\nexport function openBrowser(url: string): void {\n const os = platform();\n try {\n if (os === \"darwin\") {\n execFile(\"open\", [url]);\n } else if (os === \"win32\") {\n execFile(\"cmd\", [\"/c\", \"start\", \"\", url]);\n } else {\n execFile(\"xdg-open\", [url]);\n }\n } catch {\n // Silently ignore — caller should provide fallback instructions\n }\n}\n","import { createServer, type Server } from \"node:http\";\nimport { OAuthCallbackError, OAuthTimeoutError } from \"@/errors.ts\";\nimport type { AuthorizationResult } from \"@/types.ts\";\n\nconst SUCCESS_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Authorization Successful</title>\n<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f8f9fa}\n.card{text-align:center;padding:2rem;border-radius:12px;background:white;box-shadow:0 2px 8px rgba(0,0,0,0.1)}\nh1{color:#22c55e;font-size:1.5rem}p{color:#666}</style></head>\n<body><div class=\"card\"><h1>Authorization Successful</h1><p>You can close this window and return to the terminal.</p></div></body></html>`;\n\nconst ERROR_HTML = `<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Authorization Failed</title>\n<style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f8f9fa}\n.card{text-align:center;padding:2rem;border-radius:12px;background:white;box-shadow:0 2px 8px rgba(0,0,0,0.1)}\nh1{color:#ef4444;font-size:1.5rem}p{color:#666}</style></head>\n<body><div class=\"card\"><h1>Authorization Failed</h1><p>Something went wrong. Please try again.</p></div></body></html>`;\n\nexport interface CallbackServerOptions {\n /** Port to listen on (0 = random available port) */\n port?: number;\n /** Expected state parameter for CSRF validation */\n expectedState: string;\n /** Timeout in milliseconds (default: 120000) */\n timeout?: number;\n /** Path to listen on (default: \"/callback\") */\n path?: string;\n}\n\n/** Start a local HTTP server to receive the OAuth callback redirect */\nexport function startCallbackServer(\n options: CallbackServerOptions,\n): Promise<{ result: Promise<AuthorizationResult>; port: number; close: () => void }> {\n const { expectedState, timeout = 120_000, path = \"/callback\" } = options;\n const port = options.port ?? 0;\n\n return new Promise((resolveSetup, _rejectSetup) => {\n let server: Server;\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n const resultPromise = new Promise<AuthorizationResult>((resolveResult, rejectResult) => {\n server = createServer((req, res) => {\n const url = new URL(req.url ?? \"/\", `http://localhost`);\n\n if (url.pathname !== path) {\n res.writeHead(404);\n res.end(\"Not found\");\n return;\n }\n\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n\n if (error) {\n const errorDesc = url.searchParams.get(\"error_description\") ?? error;\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(new OAuthCallbackError(`OAuth error: ${errorDesc}`));\n return;\n }\n\n if (!code) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(new OAuthCallbackError(\"No authorization code in callback\"));\n return;\n }\n\n if (state && state !== expectedState) {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(ERROR_HTML);\n cleanup();\n rejectResult(\n new OAuthCallbackError(\"State mismatch — possible CSRF attack. Please try again.\"),\n );\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(SUCCESS_HTML);\n cleanup();\n resolveResult({ code, state: state ?? expectedState });\n });\n\n const cleanup = () => {\n if (timeoutId) clearTimeout(timeoutId);\n server.close();\n };\n\n timeoutId = setTimeout(() => {\n cleanup();\n rejectResult(new OAuthTimeoutError(timeout));\n }, timeout);\n\n server.on(\"error\", (err) => {\n cleanup();\n rejectResult(new OAuthCallbackError(`Callback server error: ${err.message}`));\n });\n\n server.listen(port, \"127.0.0.1\", () => {\n const addr = server.address();\n const actualPort = typeof addr === \"object\" && addr ? addr.port : port;\n resolveSetup({\n result: resultPromise,\n port: actualPort,\n close: cleanup,\n });\n });\n });\n });\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport type { PKCEParams } from \"@/types.ts\";\n\n/** Generate a cryptographically random PKCE code verifier (43-128 chars, base64url) */\nexport function generateCodeVerifier(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n\n/** Generate a PKCE code challenge from a verifier using S256 method */\nexport function generateCodeChallenge(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/** Generate both PKCE code verifier and challenge */\nexport function generatePKCE(): PKCEParams {\n const codeVerifier = generateCodeVerifier();\n const codeChallenge = generateCodeChallenge(codeVerifier);\n return { codeVerifier, codeChallenge };\n}\n\n/** Generate a random state parameter for CSRF protection */\nexport function generateState(): string {\n return randomBytes(32).toString(\"base64url\");\n}\n","import { createInterface } from \"node:readline\";\nimport { OAuthCallbackError, StateMismatchError } from \"@/errors.ts\";\nimport type { AuthorizationResult } from \"@/types.ts\";\n\n/**\n * Parse a \"code#state\" string (Anthropic's manual paste format).\n * Also supports a plain authorization code (state validated separately).\n */\nexport function parseCodeAndState(input: string, expectedState: string): AuthorizationResult {\n const trimmed = input.trim();\n if (!trimmed) {\n throw new OAuthCallbackError(\"Empty authorization code input\");\n }\n\n const hashIndex = trimmed.indexOf(\"#\");\n if (hashIndex !== -1) {\n const code = trimmed.slice(0, hashIndex);\n const state = trimmed.slice(hashIndex + 1);\n if (!code) {\n throw new OAuthCallbackError(\"Empty authorization code in code#state input\");\n }\n if (state !== expectedState) {\n throw new StateMismatchError();\n }\n return { code, state };\n }\n\n // No \"#\" found — assume just the code was provided\n return { code: trimmed, state: expectedState };\n}\n\n/** Prompt the user to paste the authorization code from their browser */\nexport function promptForCode(expectedState: string): Promise<AuthorizationResult> {\n return new Promise((resolve, reject) => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stderr, // Use stderr so stdout stays clean for piping\n });\n\n rl.question(\"Paste the authorization code (or code#state) from your browser: \", (answer) => {\n rl.close();\n try {\n resolve(parseCodeAndState(answer, expectedState));\n } catch (err) {\n reject(err);\n }\n });\n });\n}\n","import { OAuthCallbackError } from \"@/errors.ts\";\nimport type { AuthorizationResult, LoginOptions, ProviderConfig, TokenSet } from \"@/types.ts\";\nimport { openBrowser } from \"@/core/browser.ts\";\nimport { startCallbackServer } from \"@/core/callback-server.ts\";\nimport { generatePKCE, generateState } from \"@/core/crypto.ts\";\nimport { promptForCode } from \"@/core/manual-code-input.ts\";\n\n/** Build the full OAuth authorization URL with PKCE and state params */\nexport function buildAuthorizationUrl(\n config: ProviderConfig,\n codeChallenge: string,\n state: string,\n redirectUri: string,\n): string {\n if (!config.authorizationEndpoint) {\n throw new OAuthCallbackError(`Provider \"${config.name}\" has no authorization endpoint`);\n }\n\n const url = new URL(config.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", config.clientId);\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\"code_challenge\", codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n url.searchParams.set(\"state\", state);\n\n if (config.scopes?.length) {\n url.searchParams.set(\"scope\", config.scopes.join(\" \"));\n }\n\n return url.toString();\n}\n\n/** Exchange an authorization code for tokens */\nexport async function exchangeCode(\n config: ProviderConfig,\n code: string,\n codeVerifier: string,\n redirectUri: string,\n state?: string,\n): Promise<TokenSet> {\n const useJson = config.tokenBodyFormat === \"json\";\n\n const params: Record<string, string> = {\n grant_type: \"authorization_code\",\n code,\n code_verifier: codeVerifier,\n client_id: config.clientId,\n redirect_uri: redirectUri,\n };\n if (state !== undefined) {\n params.state = state;\n }\n\n const response = await fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": useJson ? \"application/json\" : \"application/x-www-form-urlencoded\" },\n body: useJson ? JSON.stringify(params) : new URLSearchParams(params).toString(),\n });\n\n if (!response.ok) {\n // Do not include the raw response body — it may contain sensitive OAuth debug info.\n // Consumers that need details should inspect the HTTP response directly.\n throw new OAuthCallbackError(`Token exchange failed (HTTP ${response.status})`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return parseTokenResponse(data);\n}\n\n/** Refresh an access token using a refresh token */\nexport async function refreshAccessToken(\n config: ProviderConfig,\n refreshToken: string,\n): Promise<TokenSet> {\n const useJson = config.tokenBodyFormat === \"json\";\n\n const params: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: config.clientId,\n };\n\n const response = await fetch(config.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": useJson ? \"application/json\" : \"application/x-www-form-urlencoded\" },\n body: useJson ? JSON.stringify(params) : new URLSearchParams(params).toString(),\n });\n\n if (!response.ok) {\n throw new OAuthCallbackError(`Token refresh failed (HTTP ${response.status})`);\n }\n\n const data = (await response.json()) as Record<string, unknown>;\n return parseTokenResponse(data);\n}\n\nexport interface PKCEFlowOptions {\n config: ProviderConfig;\n loginOptions?: LoginOptions;\n /**\n * Manual mode redirect URI (used when manual=true).\n * If not provided, manual mode is not available.\n */\n manualRedirectUri?: string;\n}\n\n/** Execute the full PKCE flow: generate params, open browser, wait for callback, exchange code */\nexport async function executePKCEFlow(options: PKCEFlowOptions): Promise<TokenSet> {\n const { config, loginOptions } = options;\n const { codeVerifier, codeChallenge } = generatePKCE();\n // Some providers (e.g. Claude) require state === verifier for their token exchange\n const state = config.stateIsVerifier ? codeVerifier : generateState();\n const manual = loginOptions?.manual ?? false;\n\n let authResult: AuthorizationResult;\n let redirectUri: string;\n\n if (manual && options.manualRedirectUri) {\n // Manual mode: redirect to provider's callback page, user copies code\n redirectUri = options.manualRedirectUri;\n const authUrl = buildAuthorizationUrl(config, codeChallenge, state, redirectUri);\n\n if (loginOptions?.onOpenBrowser) {\n loginOptions.onOpenBrowser(authUrl);\n } else {\n openBrowser(authUrl);\n }\n\n process.stderr.write(\n `\\nOpen this URL in your browser if it didn't open automatically:\\n${authUrl}\\n\\n`,\n );\n authResult = await promptForCode(state);\n } else {\n // Automatic mode: local callback server\n const port = loginOptions?.port ?? 0;\n const timeout = loginOptions?.timeout ?? 120_000;\n\n const server = await startCallbackServer({\n port,\n expectedState: state,\n timeout,\n });\n\n redirectUri = `http://127.0.0.1:${server.port}/callback`;\n const authUrl = buildAuthorizationUrl(config, codeChallenge, state, redirectUri);\n\n if (loginOptions?.onOpenBrowser) {\n loginOptions.onOpenBrowser(authUrl);\n } else {\n openBrowser(authUrl);\n }\n\n process.stderr.write(\n `\\nOpen this URL in your browser if it didn't open automatically:\\n${authUrl}\\n\\nWaiting for authorization...\\n`,\n );\n\n try {\n authResult = await server.result;\n } catch (err) {\n server.close();\n throw err;\n }\n }\n\n // Pass state to exchange when provider uses JSON body format (e.g. Claude)\n const exchangeState = config.tokenBodyFormat === \"json\" ? authResult.state : undefined;\n return exchangeCode(config, authResult.code, codeVerifier, redirectUri, exchangeState);\n}\n\nfunction parseTokenResponse(data: Record<string, unknown>): TokenSet {\n const accessToken = data.access_token as string | undefined;\n if (!accessToken) {\n throw new OAuthCallbackError(\"No access_token in token response\");\n }\n\n const expiresIn = (data.expires_in as number | undefined) ?? 3600;\n const expiresAt = Date.now() + expiresIn * 1000;\n\n return {\n accessToken,\n refreshToken: (data.refresh_token as string | undefined) ?? null,\n expiresAt,\n idToken: data.id_token as string | undefined,\n tokenType: ((data.token_type as string | undefined) ?? \"bearer\").toLowerCase() as\n | \"bearer\"\n | \"api-key\",\n scopes: data.scope ? (data.scope as string).split(\" \") : undefined,\n raw: data,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { r as registerProvider } from "./registry-
|
|
3
|
-
import { n as refreshAccessToken, t as executePKCEFlow } from "./oauth-pkce-
|
|
2
|
+
import { r as registerProvider } from "./registry-Dv4TWuqv.mjs";
|
|
3
|
+
import { n as refreshAccessToken, t as executePKCEFlow } from "./oauth-pkce-CCKyG5wP.mjs";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { homedir } from "node:os";
|
|
@@ -99,4 +99,4 @@ registerProvider("openai-codex", () => new OpenAICodexProvider());
|
|
|
99
99
|
//#endregion
|
|
100
100
|
export { OpenAICodexProvider, importFromCodexCli };
|
|
101
101
|
|
|
102
|
-
//# sourceMappingURL=openai-codex-
|
|
102
|
+
//# sourceMappingURL=openai-codex-CKKY1Gu_.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-codex-
|
|
1
|
+
{"version":3,"file":"openai-codex-CKKY1Gu_.mjs","names":[],"sources":["../../src/token/jwt.ts","../../src/providers/openai-codex.ts"],"sourcesContent":["/** Decoded JWT claims (no signature verification) */\nexport interface JWTClaims {\n sub?: string;\n email?: string;\n name?: string;\n iss?: string;\n aud?: string | string[];\n exp?: number;\n iat?: number;\n [key: string]: unknown;\n}\n\n/**\n * Decode a JWT token's payload without verifying the signature.\n * Used to extract user info from id_tokens (e.g., OpenAI).\n */\nexport function decodeJWT(token: string): JWTClaims {\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"Invalid JWT format: expected 3 parts\");\n }\n\n const payload = parts[1];\n if (!payload) {\n throw new Error(\"Invalid JWT: empty payload\");\n }\n\n // Add padding if needed for base64url decoding\n const padded = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const decoded = Buffer.from(padded, \"base64\").toString(\"utf8\");\n\n return JSON.parse(decoded) as JWTClaims;\n}\n","import { createHash } from \"node:crypto\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { executePKCEFlow, refreshAccessToken } from \"@/core/oauth-pkce.ts\";\nimport { decodeJWT } from \"@/token/jwt.ts\";\nimport type { AuthHeaders, LoginOptions, Provider, ProviderConfig, TokenSet } from \"@/types.ts\";\nimport { registerProvider } from \"@/providers/registry.ts\";\n\nconst OPENAI_CODEX_CONFIG: ProviderConfig = {\n name: \"openai-codex\",\n displayName: \"OpenAI ChatGPT Plus/Pro\",\n authorizationEndpoint: \"https://auth.openai.com/oauth/authorize\",\n tokenEndpoint: \"https://auth.openai.com/oauth/token\",\n clientId: \"app_EMoamEEZ73f0CkXaXp7hrann\",\n redirectUri: \"http://localhost:1455/auth/callback\",\n scopes: [\"openid\", \"profile\", \"email\", \"offline_access\"],\n grantType: \"authorization_code\",\n};\n\nexport class OpenAICodexProvider implements Provider {\n readonly config = OPENAI_CODEX_CONFIG;\n\n async login(options?: LoginOptions): Promise<TokenSet> {\n return executePKCEFlow({\n config: this.config,\n loginOptions: {\n ...options,\n port: options?.port ?? 1455,\n },\n });\n }\n\n async refresh(refreshToken: string): Promise<TokenSet> {\n return refreshAccessToken(this.config, refreshToken);\n }\n\n getAuthHeaders(accessToken: string): AuthHeaders {\n return {\n authorization: `Bearer ${accessToken}`,\n \"content-type\": \"application/json\",\n };\n }\n\n getAccountId(tokenSet: TokenSet): string {\n if (tokenSet.idToken) {\n try {\n const claims = decodeJWT(tokenSet.idToken);\n if (claims.sub) return claims.sub;\n } catch {\n // Fall through\n }\n }\n return createHash(\"sha256\").update(tokenSet.accessToken).digest(\"hex\").slice(0, 16);\n }\n\n getAccountLabel(tokenSet: TokenSet): string | undefined {\n if (tokenSet.idToken) {\n try {\n const claims = decodeJWT(tokenSet.idToken);\n if (claims.email) return claims.email as string;\n } catch {\n // Fall through\n }\n }\n return undefined;\n }\n}\n\n/**\n * Import tokens from an existing Codex CLI installation.\n * Reads from ~/.codex/auth.json or $CODEX_HOME/auth.json.\n */\nexport function importFromCodexCli(): TokenSet | null {\n const paths = [\n process.env.CODEX_HOME ? join(process.env.CODEX_HOME, \"auth.json\") : null,\n join(homedir(), \".codex\", \"auth.json\"),\n ].filter(Boolean) as string[];\n\n for (const filePath of paths) {\n if (!existsSync(filePath)) continue;\n\n try {\n const content = readFileSync(filePath, \"utf8\");\n const data = JSON.parse(content) as {\n tokens?: {\n access_token?: string;\n refresh_token?: string;\n id_token?: string;\n };\n };\n\n if (!data.tokens?.access_token) continue;\n\n return {\n accessToken: data.tokens.access_token,\n refreshToken: data.tokens.refresh_token ?? null,\n expiresAt: Date.now() + 3600_000, // Assume 1 hour, will refresh as needed\n idToken: data.tokens.id_token,\n tokenType: \"bearer\",\n };\n } catch {\n continue;\n }\n }\n\n return null;\n}\n\n// Auto-register\nregisterProvider(\"openai-codex\", () => new OpenAICodexProvider());\n"],"mappings":";;;;;;;;;;;;AAgBA,SAAgB,UAAU,OAA0B;CAClD,MAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,KAAI,MAAM,WAAW,EACnB,OAAM,IAAI,MAAM,uCAAuC;CAGzD,MAAM,UAAU,MAAM;AACtB,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,6BAA6B;CAI/C,MAAM,SAAS,QAAQ,QAAQ,MAAM,IAAI,CAAC,QAAQ,MAAM,IAAI;CAC5D,MAAM,UAAU,OAAO,KAAK,QAAQ,SAAS,CAAC,SAAS,OAAO;AAE9D,QAAO,KAAK,MAAM,QAAQ;;;;ACtB5B,MAAM,sBAAsC;CAC1C,MAAM;CACN,aAAa;CACb,uBAAuB;CACvB,eAAe;CACf,UAAU;CACV,aAAa;CACb,QAAQ;EAAC;EAAU;EAAW;EAAS;EAAiB;CACxD,WAAW;CACZ;AAED,IAAa,sBAAb,MAAqD;CACnD,SAAkB;CAElB,MAAM,MAAM,SAA2C;AACrD,SAAO,gBAAgB;GACrB,QAAQ,KAAK;GACb,cAAc;IACZ,GAAG;IACH,MAAM,SAAS,QAAQ;IACxB;GACF,CAAC;;CAGJ,MAAM,QAAQ,cAAyC;AACrD,SAAO,mBAAmB,KAAK,QAAQ,aAAa;;CAGtD,eAAe,aAAkC;AAC/C,SAAO;GACL,eAAe,UAAU;GACzB,gBAAgB;GACjB;;CAGH,aAAa,UAA4B;AACvC,MAAI,SAAS,QACX,KAAI;GACF,MAAM,SAAS,UAAU,SAAS,QAAQ;AAC1C,OAAI,OAAO,IAAK,QAAO,OAAO;UACxB;AAIV,SAAO,WAAW,SAAS,CAAC,OAAO,SAAS,YAAY,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,GAAG;;CAGrF,gBAAgB,UAAwC;AACtD,MAAI,SAAS,QACX,KAAI;GACF,MAAM,SAAS,UAAU,SAAS,QAAQ;AAC1C,OAAI,OAAO,MAAO,QAAO,OAAO;UAC1B;;;;;;;AAYd,SAAgB,qBAAsC;CACpD,MAAM,QAAQ,CACZ,QAAQ,IAAI,aAAa,KAAK,QAAQ,IAAI,YAAY,YAAY,GAAG,MACrE,KAAK,SAAS,EAAE,UAAU,YAAY,CACvC,CAAC,OAAO,QAAQ;AAEjB,MAAK,MAAM,YAAY,OAAO;AAC5B,MAAI,CAAC,WAAW,SAAS,CAAE;AAE3B,MAAI;GACF,MAAM,UAAU,aAAa,UAAU,OAAO;GAC9C,MAAM,OAAO,KAAK,MAAM,QAAQ;AAQhC,OAAI,CAAC,KAAK,QAAQ,aAAc;AAEhC,UAAO;IACL,aAAa,KAAK,OAAO;IACzB,cAAc,KAAK,OAAO,iBAAiB;IAC3C,WAAW,KAAK,KAAK,GAAG;IACxB,SAAS,KAAK,OAAO;IACrB,WAAW;IACZ;UACK;AACN;;;AAIJ,QAAO;;AAIT,iBAAiB,sBAAsB,IAAI,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as ProviderNotFoundError } from "./errors-BQeS4Q64.mjs";
|
|
3
|
+
//#region \0rolldown/runtime.js
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __exportAll = (all, no_symbols) => {
|
|
6
|
+
let target = {};
|
|
7
|
+
for (var name in all) __defProp(target, name, {
|
|
8
|
+
get: all[name],
|
|
9
|
+
enumerable: true
|
|
10
|
+
});
|
|
11
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
12
|
+
return target;
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/providers/registry.ts
|
|
16
|
+
var registry_exports = /* @__PURE__ */ __exportAll({
|
|
17
|
+
getProvider: () => getProvider,
|
|
18
|
+
listProviders: () => listProviders,
|
|
19
|
+
registerProvider: () => registerProvider
|
|
20
|
+
});
|
|
21
|
+
const providers = /* @__PURE__ */ new Map();
|
|
22
|
+
/** Register a provider factory */
|
|
23
|
+
function registerProvider(name, factory) {
|
|
24
|
+
providers.set(name, factory);
|
|
25
|
+
}
|
|
26
|
+
/** Get a provider instance by name */
|
|
27
|
+
function getProvider(name) {
|
|
28
|
+
const factory = providers.get(name);
|
|
29
|
+
if (!factory) throw new ProviderNotFoundError(name);
|
|
30
|
+
return factory();
|
|
31
|
+
}
|
|
32
|
+
/** List all registered provider names */
|
|
33
|
+
function listProviders() {
|
|
34
|
+
return Array.from(providers.keys());
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { registry_exports as i, listProviders as n, registerProvider as r, getProvider as t };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=registry-Dv4TWuqv.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry-Dv4TWuqv.mjs","names":[],"sources":["../../src/providers/registry.ts"],"sourcesContent":["import { ProviderNotFoundError } from \"@/errors.ts\";\nimport type { Provider } from \"@/types.ts\";\n\nconst providers = new Map<string, () => Provider>();\n\n/** Register a provider factory */\nexport function registerProvider(name: string, factory: () => Provider): void {\n providers.set(name, factory);\n}\n\n/** Get a provider instance by name */\nexport function getProvider(name: string): Provider {\n const factory = providers.get(name);\n if (!factory) {\n throw new ProviderNotFoundError(name);\n }\n return factory();\n}\n\n/** List all registered provider names */\nexport function listProviders(): string[] {\n return Array.from(providers.keys());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAGA,MAAM,4BAAY,IAAI,KAA6B;;AAGnD,SAAgB,iBAAiB,MAAc,SAA+B;AAC5E,WAAU,IAAI,MAAM,QAAQ;;;AAI9B,SAAgB,YAAY,MAAwB;CAClD,MAAM,UAAU,UAAU,IAAI,KAAK;AACnC,KAAI,CAAC,QACH,OAAM,IAAI,sBAAsB,KAAK;AAEvC,QAAO,SAAS;;;AAIlB,SAAgB,gBAA0B;AACxC,QAAO,MAAM,KAAK,UAAU,MAAM,CAAC"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as printError } from "./ui-CdGEuLwh.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { t as createTokenStore } from "./store-57VEqlSz.mjs";
|
|
4
|
+
import { t as TokenManager } from "./manager-C-RjavF0.mjs";
|
|
4
5
|
//#region src/cli/commands/status.ts
|
|
5
|
-
async function statusCommand() {
|
|
6
|
-
await Promise.all([import("./claude-
|
|
7
|
-
const manager = new TokenManager(await createTokenStore());
|
|
6
|
+
async function statusCommand(storeType) {
|
|
7
|
+
await Promise.all([import("./claude-Bu4Vb3Sa.mjs"), import("./openai-codex-CKKY1Gu_.mjs")]);
|
|
8
|
+
const manager = new TokenManager(await createTokenStore(storeType));
|
|
8
9
|
try {
|
|
9
10
|
const statuses = await manager.status();
|
|
10
11
|
if (statuses.length === 0) {
|
|
@@ -44,4 +45,4 @@ function formatDuration(ms) {
|
|
|
44
45
|
//#endregion
|
|
45
46
|
export { statusCommand };
|
|
46
47
|
|
|
47
|
-
//# sourceMappingURL=status-
|
|
48
|
+
//# sourceMappingURL=status-CvktIWmx.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-CvktIWmx.mjs","names":[],"sources":["../../src/cli/commands/status.ts"],"sourcesContent":["import { createTokenStore } from \"@/storage/store.ts\";\nimport type { StoreType } from \"@/storage/store.ts\";\nimport { TokenManager } from \"@/token/manager.ts\";\nimport { printError } from \"@/cli/ui.ts\";\n\nexport async function statusCommand(storeType?: StoreType): Promise<void> {\n await Promise.all([import(\"@/providers/claude.ts\"), import(\"@/providers/openai-codex.ts\")]);\n\n const store = await createTokenStore(storeType);\n const manager = new TokenManager(store);\n\n try {\n const statuses = await manager.status();\n\n if (statuses.length === 0) {\n console.log(\"No stored credentials. Run `open-sub-auth login` to get started.\");\n return;\n }\n\n // Table header\n const header = [\n \"Provider\".padEnd(22),\n \"Account\".padEnd(20),\n \"Status\".padEnd(10),\n \"Expires\",\n ].join(\" \");\n\n console.log(header);\n console.log(\"-\".repeat(header.length));\n\n for (const s of statuses) {\n const account = s.accountLabel ?? s.accountId.slice(0, 12) + \"...\";\n const expires = s.isExpired ? \"-\" : formatDuration(s.expiresAt - Date.now());\n\n console.log(\n [\n s.displayName.padEnd(22),\n account.padEnd(20),\n (s.isExpired ? \"Expired\" : \"Valid\").padEnd(10),\n expires,\n ].join(\" \"),\n );\n }\n } catch (err) {\n printError(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n\nfunction formatDuration(ms: number): string {\n if (ms <= 0) return \"-\";\n const hours = Math.floor(ms / 3600_000);\n const minutes = Math.floor((ms % 3600_000) / 60_000);\n if (hours > 0) return `${hours}h ${minutes}m`;\n return `${minutes}m`;\n}\n"],"mappings":";;;;;AAKA,eAAsB,cAAc,WAAsC;AACxE,OAAM,QAAQ,IAAI,CAAC,OAAO,0BAA0B,OAAO,+BAA+B,CAAC;CAG3F,MAAM,UAAU,IAAI,aADN,MAAM,iBAAiB,UAAU,CACR;AAEvC,KAAI;EACF,MAAM,WAAW,MAAM,QAAQ,QAAQ;AAEvC,MAAI,SAAS,WAAW,GAAG;AACzB,WAAQ,IAAI,mEAAmE;AAC/E;;EAIF,MAAM,SAAS;GACb,WAAW,OAAO,GAAG;GACrB,UAAU,OAAO,GAAG;GACpB,SAAS,OAAO,GAAG;GACnB;GACD,CAAC,KAAK,KAAK;AAEZ,UAAQ,IAAI,OAAO;AACnB,UAAQ,IAAI,IAAI,OAAO,OAAO,OAAO,CAAC;AAEtC,OAAK,MAAM,KAAK,UAAU;GACxB,MAAM,UAAU,EAAE,gBAAgB,EAAE,UAAU,MAAM,GAAG,GAAG,GAAG;GAC7D,MAAM,UAAU,EAAE,YAAY,MAAM,eAAe,EAAE,YAAY,KAAK,KAAK,CAAC;AAE5E,WAAQ,IACN;IACE,EAAE,YAAY,OAAO,GAAG;IACxB,QAAQ,OAAO,GAAG;KACjB,EAAE,YAAY,YAAY,SAAS,OAAO,GAAG;IAC9C;IACD,CAAC,KAAK,KAAK,CACb;;UAEI,KAAK;AACZ,aAAW,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACvF,UAAQ,KAAK,EAAE;;;AAInB,SAAS,eAAe,IAAoB;AAC1C,KAAI,MAAM,EAAG,QAAO;CACpB,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAS;CACvC,MAAM,UAAU,KAAK,MAAO,KAAK,OAAY,IAAO;AACpD,KAAI,QAAQ,EAAG,QAAO,GAAG,MAAM,IAAI,QAAQ;AAC3C,QAAO,GAAG,QAAQ"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { t as AuthenticationError } from "./errors-BQeS4Q64.mjs";
|
|
3
3
|
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
4
4
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { hostname, userInfo } from "node:os";
|
|
@@ -191,141 +191,25 @@ var KeychainStore = class {
|
|
|
191
191
|
//#endregion
|
|
192
192
|
//#region src/storage/store.ts
|
|
193
193
|
/**
|
|
194
|
-
* Create a token store
|
|
195
|
-
*
|
|
194
|
+
* Create a token store.
|
|
195
|
+
*
|
|
196
|
+
* @param storeType
|
|
197
|
+
* - `"auto"` (default): try OS keychain first, fall back to encrypted file store
|
|
198
|
+
* - `"keychain"`: force OS keychain; throws AuthenticationError if unavailable
|
|
199
|
+
* - `"file"`: always use the encrypted file store (~/.open-sub-auth/credentials.json)
|
|
196
200
|
*/
|
|
197
|
-
async function createTokenStore(
|
|
198
|
-
if (
|
|
201
|
+
async function createTokenStore(storeType = "auto") {
|
|
202
|
+
if (storeType === "file") return new FileStore();
|
|
203
|
+
try {
|
|
199
204
|
const store = new KeychainStore();
|
|
200
205
|
await store.get("__probe__", "__probe__");
|
|
201
206
|
return store;
|
|
202
|
-
} catch {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
//#endregion
|
|
206
|
-
//#region src/token/manager.ts
|
|
207
|
-
/** Buffer time before expiry to trigger refresh (5 minutes) */
|
|
208
|
-
const REFRESH_BUFFER_MS = 300 * 1e3;
|
|
209
|
-
/** Central token lifecycle manager */
|
|
210
|
-
var TokenManager = class {
|
|
211
|
-
store;
|
|
212
|
-
/** Per-provider+account mutex to prevent concurrent refreshes */
|
|
213
|
-
refreshLocks = /* @__PURE__ */ new Map();
|
|
214
|
-
constructor(store) {
|
|
215
|
-
this.store = store;
|
|
216
|
-
}
|
|
217
|
-
/** Run the interactive login flow for a provider, store the tokens */
|
|
218
|
-
async login(providerName, options) {
|
|
219
|
-
const provider = getProvider(providerName);
|
|
220
|
-
const tokenSet = await provider.login(options);
|
|
221
|
-
const accountId = provider.getAccountId(tokenSet);
|
|
222
|
-
const accountLabel = provider.getAccountLabel?.(tokenSet);
|
|
223
|
-
const now = Date.now();
|
|
224
|
-
const credential = {
|
|
225
|
-
tokenSet,
|
|
226
|
-
metadata: {
|
|
227
|
-
provider: providerName,
|
|
228
|
-
accountId,
|
|
229
|
-
accountLabel,
|
|
230
|
-
createdAt: now,
|
|
231
|
-
lastRefreshedAt: now
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
await this.store.set(providerName, accountId, credential);
|
|
235
|
-
return credential;
|
|
236
|
-
}
|
|
237
|
-
/** Get a valid access token, refreshing if needed */
|
|
238
|
-
async getToken(providerName, accountId) {
|
|
239
|
-
const credential = await this.resolveCredential(providerName, accountId);
|
|
240
|
-
const { tokenSet } = credential;
|
|
241
|
-
if (this.isTokenValid(tokenSet.expiresAt)) return tokenSet.accessToken;
|
|
242
|
-
if (!tokenSet.refreshToken) throw new TokenExpiredError(providerName);
|
|
243
|
-
await this.refreshWithLock(providerName, credential);
|
|
244
|
-
const refreshed = await this.store.get(providerName, credential.metadata.accountId);
|
|
245
|
-
if (!refreshed) throw new TokenExpiredError(providerName);
|
|
246
|
-
return refreshed.tokenSet.accessToken;
|
|
247
|
-
}
|
|
248
|
-
/** Get authenticated headers for API calls */
|
|
249
|
-
async getAuthHeaders(providerName, accountId) {
|
|
250
|
-
const token = await this.getToken(providerName, accountId);
|
|
251
|
-
return getProvider(providerName).getAuthHeaders(token);
|
|
252
|
-
}
|
|
253
|
-
/** Remove stored tokens for a provider */
|
|
254
|
-
async logout(providerName, accountId) {
|
|
255
|
-
if (accountId) await this.store.delete(providerName, accountId);
|
|
256
|
-
else {
|
|
257
|
-
const credentials = await this.store.list(providerName);
|
|
258
|
-
for (const cred of credentials) await this.store.delete(providerName, cred.metadata.accountId);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
/** Get status of all stored credentials */
|
|
262
|
-
async status() {
|
|
263
|
-
return (await this.store.list()).map((cred) => {
|
|
264
|
-
let displayName = cred.metadata.provider;
|
|
265
|
-
try {
|
|
266
|
-
displayName = getProvider(cred.metadata.provider).config.displayName;
|
|
267
|
-
} catch {}
|
|
268
|
-
return {
|
|
269
|
-
provider: cred.metadata.provider,
|
|
270
|
-
displayName,
|
|
271
|
-
accountId: cred.metadata.accountId,
|
|
272
|
-
accountLabel: cred.metadata.accountLabel,
|
|
273
|
-
isExpired: !this.isTokenValid(cred.tokenSet.expiresAt),
|
|
274
|
-
expiresAt: cred.tokenSet.expiresAt,
|
|
275
|
-
hasRefreshToken: cred.tokenSet.refreshToken !== null
|
|
276
|
-
};
|
|
277
|
-
});
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (storeType === "keychain") throw new AuthenticationError(`OS keychain is not available on this system: ${err instanceof Error ? err.message : String(err)}`);
|
|
209
|
+
return new FileStore();
|
|
278
210
|
}
|
|
279
|
-
|
|
280
|
-
return Date.now() + REFRESH_BUFFER_MS < expiresAt;
|
|
281
|
-
}
|
|
282
|
-
async resolveCredential(providerName, accountId) {
|
|
283
|
-
if (accountId) {
|
|
284
|
-
const credential = await this.store.get(providerName, accountId);
|
|
285
|
-
if (!credential) throw new NoCredentialError(providerName, accountId);
|
|
286
|
-
return credential;
|
|
287
|
-
}
|
|
288
|
-
const credentials = await this.store.list(providerName);
|
|
289
|
-
if (credentials.length === 0) throw new NoCredentialError(providerName);
|
|
290
|
-
return credentials.sort((a, b) => b.metadata.lastRefreshedAt - a.metadata.lastRefreshedAt)[0];
|
|
291
|
-
}
|
|
292
|
-
async refreshWithLock(providerName, credential) {
|
|
293
|
-
const lockKey = `${providerName}::${credential.metadata.accountId}`;
|
|
294
|
-
const existingLock = this.refreshLocks.get(lockKey);
|
|
295
|
-
if (existingLock) {
|
|
296
|
-
await existingLock;
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
const refreshPromise = this.doRefresh(providerName, credential);
|
|
300
|
-
this.refreshLocks.set(lockKey, refreshPromise);
|
|
301
|
-
try {
|
|
302
|
-
await refreshPromise;
|
|
303
|
-
} finally {
|
|
304
|
-
this.refreshLocks.delete(lockKey);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
async doRefresh(providerName, credential) {
|
|
308
|
-
const provider = getProvider(providerName);
|
|
309
|
-
const { refreshToken } = credential.tokenSet;
|
|
310
|
-
if (!refreshToken) throw new TokenExpiredError(providerName);
|
|
311
|
-
let newTokenSet;
|
|
312
|
-
try {
|
|
313
|
-
newTokenSet = await provider.refresh(refreshToken);
|
|
314
|
-
} catch (err) {
|
|
315
|
-
throw new TokenRefreshError(providerName, err);
|
|
316
|
-
}
|
|
317
|
-
if (!newTokenSet.refreshToken && refreshToken) newTokenSet.refreshToken = refreshToken;
|
|
318
|
-
const updatedCredential = {
|
|
319
|
-
tokenSet: newTokenSet,
|
|
320
|
-
metadata: {
|
|
321
|
-
...credential.metadata,
|
|
322
|
-
lastRefreshedAt: Date.now()
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
await this.store.set(providerName, credential.metadata.accountId, updatedCredential);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
211
|
+
}
|
|
328
212
|
//#endregion
|
|
329
|
-
export { createTokenStore as
|
|
213
|
+
export { createTokenStore as t };
|
|
330
214
|
|
|
331
|
-
//# sourceMappingURL=
|
|
215
|
+
//# sourceMappingURL=store-57VEqlSz.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-57VEqlSz.mjs","names":["homedir"],"sources":["../../src/storage/file-store.ts","../../src/storage/keychain-store.ts","../../src/storage/store.ts"],"sourcesContent":["import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from \"node:crypto\";\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { hostname, userInfo } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { StoredCredential, TokenStore } from \"@/types.ts\";\n\nconst ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32;\nconst IV_LENGTH = 12;\nconst SALT_LENGTH = 16;\nconst PBKDF2_ITERATIONS = 100_000;\nconst AUTH_TAG_LENGTH = 16;\n\ninterface EncryptedEntry {\n data: string; // base64 encoded ciphertext + auth tag\n iv: string; // base64\n salt: string; // base64\n}\n\ninterface FileStoreData {\n version: 1;\n credentials: Record<string, EncryptedEntry>;\n}\n\n/** Token store backed by an encrypted JSON file */\nexport class FileStore implements TokenStore {\n private readonly filePath: string;\n\n constructor(filePath?: string) {\n this.filePath =\n filePath ??\n join(process.env.OPEN_SUB_AUTH_HOME ?? join(homedir(), \".open-sub-auth\"), \"credentials.json\");\n }\n\n async get(provider: string, accountId: string): Promise<StoredCredential | null> {\n const data = this.readFile();\n const key = `${provider}::${accountId}`;\n const entry = data.credentials[key];\n if (!entry) return null;\n\n try {\n const decrypted = this.decrypt(entry);\n return JSON.parse(decrypted) as StoredCredential;\n } catch {\n return null;\n }\n }\n\n async set(provider: string, accountId: string, credential: StoredCredential): Promise<void> {\n const data = this.readFile();\n const key = `${provider}::${accountId}`;\n data.credentials[key] = this.encrypt(JSON.stringify(credential));\n this.writeFile(data);\n }\n\n async delete(provider: string, accountId: string): Promise<void> {\n const data = this.readFile();\n const key = `${provider}::${accountId}`;\n delete data.credentials[key];\n this.writeFile(data);\n }\n\n async list(provider?: string): Promise<StoredCredential[]> {\n const data = this.readFile();\n const results: StoredCredential[] = [];\n\n for (const [key, entry] of Object.entries(data.credentials)) {\n const [keyProvider] = key.split(\"::\");\n if (provider && keyProvider !== provider) continue;\n\n try {\n const decrypted = this.decrypt(entry);\n results.push(JSON.parse(decrypted) as StoredCredential);\n } catch {\n // Skip corrupted entries\n }\n }\n\n return results;\n }\n\n private deriveKey(salt: Buffer): Buffer {\n const machineId = `${hostname()}:${userInfo().username}`;\n return pbkdf2Sync(machineId, salt, PBKDF2_ITERATIONS, KEY_LENGTH, \"sha512\");\n }\n\n private encrypt(plaintext: string): EncryptedEntry {\n const salt = randomBytes(SALT_LENGTH);\n const key = this.deriveKey(salt);\n const iv = randomBytes(IV_LENGTH);\n\n const cipher = createCipheriv(ALGORITHM, key, iv, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n const encrypted = Buffer.concat([\n cipher.update(plaintext, \"utf8\"),\n cipher.final(),\n cipher.getAuthTag(),\n ]);\n\n return {\n data: encrypted.toString(\"base64\"),\n iv: iv.toString(\"base64\"),\n salt: salt.toString(\"base64\"),\n };\n }\n\n private decrypt(entry: EncryptedEntry): string {\n const salt = Buffer.from(entry.salt, \"base64\");\n const key = this.deriveKey(salt);\n const iv = Buffer.from(entry.iv, \"base64\");\n const raw = Buffer.from(entry.data, \"base64\");\n\n const authTag = raw.subarray(raw.length - AUTH_TAG_LENGTH);\n const ciphertext = raw.subarray(0, raw.length - AUTH_TAG_LENGTH);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv, {\n authTagLength: AUTH_TAG_LENGTH,\n });\n decipher.setAuthTag(authTag);\n\n return decipher.update(ciphertext) + decipher.final(\"utf8\");\n }\n\n private readFile(): FileStoreData {\n try {\n const content = readFileSync(this.filePath, \"utf8\");\n return JSON.parse(content) as FileStoreData;\n } catch {\n return { version: 1, credentials: {} };\n }\n }\n\n private writeFile(data: FileStoreData): void {\n mkdirSync(dirname(this.filePath), { recursive: true, mode: 0o700 });\n writeFileSync(this.filePath, JSON.stringify(data, null, 2), { mode: 0o600 });\n }\n}\n\nfunction homedir(): string {\n return process.env.HOME ?? process.env.USERPROFILE ?? \"/tmp\";\n}\n","import type { StoredCredential, TokenStore } from \"@/types.ts\";\n\nconst SERVICE_NAME = \"open-sub-auth\";\nconst INDEX_ACCOUNT = \"__index__\";\n\n/** Token store backed by the OS keychain via cross-keychain */\nexport class KeychainStore implements TokenStore {\n private keychain: typeof import(\"cross-keychain\") | null = null;\n\n private async getKeychain() {\n if (!this.keychain) {\n this.keychain = await import(\"cross-keychain\");\n }\n return this.keychain;\n }\n\n private makeKey(provider: string, accountId: string): string {\n return `${provider}__${accountId}`;\n }\n\n async get(provider: string, accountId: string): Promise<StoredCredential | null> {\n const kc = await this.getKeychain();\n try {\n const value = await kc.getPassword(SERVICE_NAME, this.makeKey(provider, accountId));\n if (!value) return null;\n return JSON.parse(value) as StoredCredential;\n } catch {\n return null;\n }\n }\n\n async set(provider: string, accountId: string, credential: StoredCredential): Promise<void> {\n const kc = await this.getKeychain();\n const key = this.makeKey(provider, accountId);\n const value = JSON.stringify(credential);\n\n try {\n await kc.deletePassword(SERVICE_NAME, key);\n } catch {\n // Ignore if it doesn't exist\n }\n await kc.setPassword(SERVICE_NAME, key, value);\n await this.addToIndex(key);\n }\n\n async delete(provider: string, accountId: string): Promise<void> {\n const kc = await this.getKeychain();\n const key = this.makeKey(provider, accountId);\n try {\n await kc.deletePassword(SERVICE_NAME, key);\n } catch {\n // Ignore if it doesn't exist\n }\n await this.removeFromIndex(key);\n }\n\n async list(provider?: string): Promise<StoredCredential[]> {\n const index = await this.getIndex();\n const results: StoredCredential[] = [];\n\n for (const key of index) {\n const [keyProvider, keyAccountId] = key.split(\"__\");\n if (provider && keyProvider !== provider) continue;\n if (!keyProvider || !keyAccountId) continue;\n\n const credential = await this.get(keyProvider, keyAccountId);\n if (credential) {\n results.push(credential);\n }\n }\n\n return results;\n }\n\n private async getIndex(): Promise<string[]> {\n const kc = await this.getKeychain();\n try {\n const value = await kc.getPassword(SERVICE_NAME, INDEX_ACCOUNT);\n if (!value) return [];\n return JSON.parse(value) as string[];\n } catch {\n return [];\n }\n }\n\n private async setIndex(index: string[]): Promise<void> {\n const kc = await this.getKeychain();\n try {\n await kc.deletePassword(SERVICE_NAME, INDEX_ACCOUNT);\n } catch {\n // Ignore\n }\n await kc.setPassword(SERVICE_NAME, INDEX_ACCOUNT, JSON.stringify(index));\n }\n\n private async addToIndex(key: string): Promise<void> {\n const index = await this.getIndex();\n if (!index.includes(key)) {\n index.push(key);\n await this.setIndex(index);\n }\n }\n\n private async removeFromIndex(key: string): Promise<void> {\n const index = await this.getIndex();\n const filtered = index.filter((k) => k !== key);\n await this.setIndex(filtered);\n }\n}\n","import { AuthenticationError } from \"@/errors.ts\";\nimport type { TokenStore } from \"@/types.ts\";\nimport { FileStore } from \"@/storage/file-store.ts\";\nimport { KeychainStore } from \"@/storage/keychain-store.ts\";\n\nexport type StoreType = \"auto\" | \"keychain\" | \"file\";\n\n/**\n * Create a token store.\n *\n * @param storeType\n * - `\"auto\"` (default): try OS keychain first, fall back to encrypted file store\n * - `\"keychain\"`: force OS keychain; throws AuthenticationError if unavailable\n * - `\"file\"`: always use the encrypted file store (~/.open-sub-auth/credentials.json)\n */\nexport async function createTokenStore(storeType: StoreType = \"auto\"): Promise<TokenStore> {\n if (storeType === \"file\") {\n return new FileStore();\n }\n\n try {\n const store = new KeychainStore();\n // Probe if keychain is accessible\n await store.get(\"__probe__\", \"__probe__\");\n return store;\n } catch (err) {\n if (storeType === \"keychain\") {\n throw new AuthenticationError(\n `OS keychain is not available on this system: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n // \"auto\" mode: fall back silently\n return new FileStore();\n }\n}\n\nexport { FileStore, KeychainStore };\n"],"mappings":"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"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as listProviders } from "./registry-
|
|
2
|
+
import { n as listProviders } from "./registry-Dv4TWuqv.mjs";
|
|
3
3
|
import { r as promptSelect, t as printError } from "./ui-CdGEuLwh.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { t as createTokenStore } from "./store-57VEqlSz.mjs";
|
|
5
|
+
import { t as TokenManager } from "./manager-C-RjavF0.mjs";
|
|
5
6
|
//#region src/cli/commands/token.ts
|
|
6
|
-
async function tokenCommand(providerArg) {
|
|
7
|
-
await Promise.all([import("./claude-
|
|
7
|
+
async function tokenCommand(providerArg, storeType) {
|
|
8
|
+
await Promise.all([import("./claude-Bu4Vb3Sa.mjs"), import("./openai-codex-CKKY1Gu_.mjs")]);
|
|
8
9
|
const providers = listProviders();
|
|
9
10
|
let providerName = providerArg;
|
|
10
11
|
if (!providerName) providerName = await promptSelect("Select a provider:", providers);
|
|
11
|
-
const manager = new TokenManager(await createTokenStore());
|
|
12
|
+
const manager = new TokenManager(await createTokenStore(storeType));
|
|
12
13
|
try {
|
|
13
14
|
const token = await manager.getToken(providerName);
|
|
14
15
|
process.stdout.write(token);
|
|
@@ -20,4 +21,4 @@ async function tokenCommand(providerArg) {
|
|
|
20
21
|
//#endregion
|
|
21
22
|
export { tokenCommand };
|
|
22
23
|
|
|
23
|
-
//# sourceMappingURL=token-
|
|
24
|
+
//# sourceMappingURL=token-Ce_FmqGM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-Ce_FmqGM.mjs","names":[],"sources":["../../src/cli/commands/token.ts"],"sourcesContent":["import { listProviders } from \"@/providers/registry.ts\";\nimport { createTokenStore } from \"@/storage/store.ts\";\nimport type { StoreType } from \"@/storage/store.ts\";\nimport { TokenManager } from \"@/token/manager.ts\";\nimport { printError, promptSelect } from \"@/cli/ui.ts\";\n\nexport async function tokenCommand(providerArg?: string, storeType?: StoreType): Promise<void> {\n await Promise.all([import(\"@/providers/claude.ts\"), import(\"@/providers/openai-codex.ts\")]);\n\n const providers = listProviders();\n let providerName = providerArg;\n if (!providerName) {\n providerName = await promptSelect(\"Select a provider:\", providers);\n }\n\n const store = await createTokenStore(storeType);\n const manager = new TokenManager(store);\n\n try {\n const token = await manager.getToken(providerName);\n process.stdout.write(token);\n } catch (err) {\n printError(`Failed to get token: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;AAMA,eAAsB,aAAa,aAAsB,WAAsC;AAC7F,OAAM,QAAQ,IAAI,CAAC,OAAO,0BAA0B,OAAO,+BAA+B,CAAC;CAE3F,MAAM,YAAY,eAAe;CACjC,IAAI,eAAe;AACnB,KAAI,CAAC,aACH,gBAAe,MAAM,aAAa,sBAAsB,UAAU;CAIpE,MAAM,UAAU,IAAI,aADN,MAAM,iBAAiB,UAAU,CACR;AAEvC,KAAI;EACF,MAAM,QAAQ,MAAM,QAAQ,SAAS,aAAa;AAClD,UAAQ,OAAO,MAAM,MAAM;UACpB,KAAK;AACZ,aAAW,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACtF,UAAQ,KAAK,EAAE"}
|