From 91af81731ffb3a7b66e6d61be00cd6057caf9890 Mon Sep 17 00:00:00 2001 From: Neil Daquioag <405533+ndycode@users.noreply.github.com> Date: Sat, 28 Feb 2026 07:42:09 +0800 Subject: [PATCH] fix(auth): harden callback parsing and loopback redirect URI --- index.ts | 2 +- lib/auth/auth.ts | 6 +++++- test/auth.test.ts | 11 ++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/index.ts b/index.ts index 4f7a4a59..a7aac5e5 100644 --- a/index.ts +++ b/index.ts @@ -389,7 +389,7 @@ export const OpenAIOAuthPlugin: Plugin = async ({ client }: PluginInput) => { validate: (input: string): string | undefined => { const parsed = parseAuthorizationInput(input); if (!parsed.code) { - return "No authorization code found. Paste the full callback URL (e.g., http://localhost:1455/auth/callback?code=...)"; + return `No authorization code found. Paste the full callback URL (e.g., ${REDIRECT_URI}?code=...)`; } if (!parsed.state) { return "Missing OAuth state. Paste the full callback URL including both code and state parameters."; diff --git a/lib/auth/auth.ts b/lib/auth/auth.ts index 545d6365..295acedb 100644 --- a/lib/auth/auth.ts +++ b/lib/auth/auth.ts @@ -8,7 +8,7 @@ import { safeParseOAuthTokenResponse } from "../schemas.js"; export const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"; export const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize"; export const TOKEN_URL = "https://auth.openai.com/oauth/token"; -export const REDIRECT_URI = "http://localhost:1455/auth/callback"; +export const REDIRECT_URI = "http://127.0.0.1:1455/auth/callback"; export const SCOPE = "openid profile email offline_access"; /** @@ -44,6 +44,10 @@ export function parseAuthorizationInput(input: string): ParsedAuthInput { if (code || state) { return { code, state }; } + + // Input is a valid URL but does not contain OAuth parameters. + // Do not reinterpret URL fragments as "code#state" fallback syntax. + return {}; } catch { // Invalid URL, try other parsing methods } diff --git a/test/auth.test.ts b/test/auth.test.ts index 3f8b1005..2b34e9ba 100644 --- a/test/auth.test.ts +++ b/test/auth.test.ts @@ -115,13 +115,10 @@ describe('Auth Module', () => { expect(result).toEqual({}); }); - it('should fall through to # split when valid URL has hash with no code/state params (line 44 false branch)', () => { - // URL parses successfully but hash contains no code= or state= params - // Line 44's false branch is hit (code && state both undefined) - // Falls through to line 51 which splits on # + it('should return empty object for valid URL hash fragments without OAuth params', () => { const input = 'http://localhost:1455/auth/callback#invalid'; const result = parseAuthorizationInput(input); - expect(result).toEqual({ code: 'http://localhost:1455/auth/callback', state: 'invalid' }); + expect(result).toEqual({}); }); }); @@ -178,6 +175,10 @@ describe('Auth Module', () => { }); describe('createAuthorizationFlow', () => { + it('uses explicit loopback redirect URI to avoid localhost IPv6 ambiguity', () => { + expect(REDIRECT_URI).toBe('http://127.0.0.1:1455/auth/callback'); + }); + it('should create authorization flow with PKCE', async () => { const flow = await createAuthorizationFlow();