Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 64 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,18 @@ export const OpenAIAuthPlugin: Plugin = async ({ client }: PluginInput) => {
label: AUTH_LABELS.OAUTH,
type: "oauth" as const,
/**
* OAuth authorization flow
* OAuth authorization flow with automatic localhost callback
*
* Steps:
* 1. Generate PKCE challenge and state for security
* 2. Start local OAuth callback server on port 1455
* 3. Open browser to OpenAI authorization page
* 4. Wait for user to complete login
* 4. Wait for callback to localhost server
* 5. Exchange authorization code for tokens
*
* This is the default flow that works when the browser can
* reach localhost:1455. If this fails, use the manual paste option.
*
* @returns Authorization flow configuration
*/
authorize: async () => {
Expand Down Expand Up @@ -259,6 +262,65 @@ export const OpenAIAuthPlugin: Plugin = async ({ client }: PluginInput) => {
};
},
},
{
label: "ChatGPT Plus/Pro (Manual URL Paste)",
type: "oauth" as const,
/**
* OAuth authorization flow with manual URL paste
*
* Steps:
* 1. Generate PKCE challenge and state for security
* 2. Open browser to OpenAI authorization page
* 3. User completes login and copies redirect URL
* 4. User pastes URL back into terminal
* 5. Exchange authorization code for tokens
*
* Use this flow when localhost callbacks don't work:
* - Remote servers (SSH)
* - Containers/Docker
* - WSL without localhost forwarding
*
* @returns Authorization flow configuration
*/
authorize: async () => {
const { pkce, url } = await createAuthorizationFlow();

// Attempt to open browser automatically
openBrowserUrl(url);

return {
url,
method: "code" as const,
instructions: "1. Open the URL above in your browser and complete login.\n2. After login, your browser will redirect to localhost (which may fail to load).\n3. Copy the FULL URL from your browser's address bar and paste it below.\n (It looks like: http://localhost:1455/auth/callback?code=...&state=...)",
callback: async (input: string) => {
const { parseAuthorizationInput } = await import("./lib/auth/auth.js");
Copy link

Copilot AI Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a dynamic import for parseAuthorizationInput is inconsistent with other auth functions that are imported at the top of the file. For consistency and better code organization, consider adding parseAuthorizationInput to the static imports at line 27-32 where other auth functions like createAuthorizationFlow and exchangeAuthorizationCode are imported.

This would make the code more maintainable and eliminate the async overhead of the dynamic import in the callback.

Copilot uses AI. Check for mistakes.
const parsed = parseAuthorizationInput(input);

if (!parsed.code) {
console.error("[openai-codex-plugin] No authorization code found in input");
return { type: "failed" as const };
}

// Validate state parameter to prevent CSRF and authorization code substitution
if (!parsed.state || parsed.state !== state) {
console.error(
"[openai-codex-plugin] Invalid or missing state parameter in authorization response",
);
return { type: "failed" as const };
}
const tokens = await exchangeAuthorizationCode(
parsed.code,
pkce.verifier,
REDIRECT_URI,
);

return tokens?.type === "success"
? tokens
: { type: "failed" as const };
},
};
},
},
{
label: AUTH_LABELS.API_KEY,
type: "api" as const,
Expand Down