Skip to content

Commit e950c9b

Browse files
authored
Merge pull request #8 from ndycode/fix/multi-account-duplicate-accountid
fix: force fresh login when adding accounts to prevent duplicate accountIDs
2 parents 777d0f0 + 4c9006f commit e950c9b

4 files changed

Lines changed: 65 additions & 13 deletions

File tree

index.ts

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@ export const OpenAIAuthPlugin: Plugin = async ({ client }: PluginInput) => {
162162
);
163163
};
164164

165-
const runOAuthFlow = async (
166-
useManualMode: boolean,
167-
): Promise<TokenResult> => {
168-
const { pkce, state, url } = await createAuthorizationFlow();
165+
const runOAuthFlow = async (
166+
useManualMode: boolean,
167+
forceNewLogin: boolean = false,
168+
): Promise<TokenResult> => {
169+
const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
169170
console.log("\nOAuth URL:\n" + url + "\n");
170171

171172
if (useManualMode) {
@@ -797,14 +798,40 @@ export const OpenAIAuthPlugin: Plugin = async ({ client }: PluginInput) => {
797798
}
798799
}
799800

800-
while (accounts.length < ACCOUNT_LIMITS.MAX_ACCOUNTS) {
801-
console.log(
802-
`\n=== OpenAI OAuth (Account ${
803-
accounts.length + 1
804-
}) ===`,
805-
);
806-
const result = await runOAuthFlow(useManualMode);
807-
if (result.type === "failed") {
801+
while (accounts.length < ACCOUNT_LIMITS.MAX_ACCOUNTS) {
802+
console.log(
803+
`\n=== OpenAI OAuth (Account ${
804+
accounts.length + 1
805+
}) ===`,
806+
);
807+
808+
const forceNewLogin = accounts.length > 0;
809+
const result = await runOAuthFlow(useManualMode, forceNewLogin);
810+
811+
if (result.type === "success") {
812+
const email = extractAccountEmail(result.access);
813+
const accountId = extractAccountId(result.access);
814+
const label = email || accountId || "Unknown account";
815+
console.log(`\n✓ Authenticated as: ${label}\n`);
816+
817+
const isDuplicate = accounts.some(
818+
(acc) =>
819+
(accountId && extractAccountId(acc.access) === accountId) ||
820+
(email && extractAccountEmail(acc.access) === email),
821+
);
822+
823+
if (isDuplicate) {
824+
console.warn(
825+
`\n⚠️ WARNING: You authenticated with an account that is already in the list (${label}).`,
826+
);
827+
console.warn(
828+
"This usually happens if you didn't log out or use a different browser profile.",
829+
);
830+
console.warn("The duplicate will update the existing entry.\n");
831+
}
832+
}
833+
834+
if (result.type === "failed") {
808835
if (accounts.length === 0) {
809836
return {
810837
url: "",

lib/auth/auth.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,20 @@ export async function refreshAccessToken(refreshToken: string): Promise<TokenRes
180180
}
181181
}
182182

183+
export interface AuthorizationFlowOptions {
184+
/**
185+
* Force a fresh login screen instead of using cached browser session.
186+
* Use when adding multiple accounts to ensure different credentials.
187+
*/
188+
forceNewLogin?: boolean;
189+
}
190+
183191
/**
184192
* Create OAuth authorization flow
193+
* @param options - Optional configuration for the flow
185194
* @returns Authorization flow details
186195
*/
187-
export async function createAuthorizationFlow(): Promise<AuthorizationFlow> {
196+
export async function createAuthorizationFlow(options?: AuthorizationFlowOptions): Promise<AuthorizationFlow> {
188197
const pkce = (await generatePKCE()) as PKCEPair;
189198
const state = createState();
190199

@@ -200,5 +209,11 @@ export async function createAuthorizationFlow(): Promise<AuthorizationFlow> {
200209
url.searchParams.set("codex_cli_simplified_flow", "true");
201210
url.searchParams.set("originator", "codex_cli_rs");
202211

212+
// Force a fresh login screen when adding multiple accounts
213+
// This helps prevent the browser from auto-using an existing session
214+
if (options?.forceNewLogin) {
215+
url.searchParams.set("prompt", "login");
216+
}
217+
203218
return { pkce, state, url: url.toString() };
204219
}

lib/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ export async function promptAddAnotherAccount(
66
): Promise<boolean> {
77
const rl = createInterface({ input, output });
88
try {
9+
console.log(
10+
"\n⚠️ TIP: Use incognito/private browsing or log out of ChatGPT before adding another account.\n",
11+
);
912
const answer = await rl.question(
1013
`Add another account? (${currentCount} added) (y/n): `,
1114
);

test/auth.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ describe('Auth Module', () => {
141141
expect(url.searchParams.get('id_token_add_organizations')).toBe('true');
142142
expect(url.searchParams.get('codex_cli_simplified_flow')).toBe('true');
143143
expect(url.searchParams.get('originator')).toBe('codex_cli_rs');
144+
expect(url.searchParams.has('prompt')).toBe(false);
145+
});
146+
147+
it('should include prompt=login when forceNewLogin is true', async () => {
148+
const flow = await createAuthorizationFlow({ forceNewLogin: true });
149+
const url = new URL(flow.url);
150+
expect(url.searchParams.get('prompt')).toBe('login');
144151
});
145152

146153
it('should generate unique flows', async () => {

0 commit comments

Comments
 (0)