codex-multi-auth 2.2.2 → 2.3.0-beta.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/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -0
- package/dist/lib/accounts.d.ts +26 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +71 -0
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/codex-manager/account-pool-write.d.ts +95 -0
- package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -0
- package/dist/lib/codex-manager/account-pool-write.js +166 -0
- package/dist/lib/codex-manager/account-pool-write.js.map +1 -0
- package/dist/lib/codex-manager/manual-callback.d.ts +33 -0
- package/dist/lib/codex-manager/manual-callback.d.ts.map +1 -0
- package/dist/lib/codex-manager/manual-callback.js +31 -0
- package/dist/lib/codex-manager/manual-callback.js.map +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +77 -85
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts +18 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +27 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +1 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +81 -6
- package/dist/lib/runtime-rotation-proxy.js.map +1 -1
- package/dist/lib/schemas.d.ts +4 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +1 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/ui/copy.d.ts +2 -0
- package/dist/lib/ui/copy.d.ts.map +1 -1
- package/dist/lib/ui/copy.js +2 -0
- package/dist/lib/ui/copy.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choose the workspace a freshly-saved account should start on: prefer the
|
|
3
|
+
* workspace whose id matches the resolved accountId, otherwise the first
|
|
4
|
+
* enabled workspace, otherwise index 0.
|
|
5
|
+
*/
|
|
6
|
+
export function pickInitialWorkspaceIndex(workspaces, accountId) {
|
|
7
|
+
if (accountId) {
|
|
8
|
+
const matching = workspaces.findIndex((workspace) => workspace.id === accountId);
|
|
9
|
+
if (matching >= 0)
|
|
10
|
+
return matching;
|
|
11
|
+
}
|
|
12
|
+
const firstEnabled = workspaces.findIndex((workspace) => workspace.enabled !== false);
|
|
13
|
+
return firstEnabled >= 0 ? firstEnabled : 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Merge incoming workspaces with the saved set, preserving any per-workspace
|
|
17
|
+
* enabled/disabled state the user already had. Workspaces the login did not
|
|
18
|
+
* surface are dropped only when the login returned a workspace list at all;
|
|
19
|
+
* otherwise the existing list is kept untouched.
|
|
20
|
+
*/
|
|
21
|
+
export function mergeAccountWorkspaces(existing, incoming) {
|
|
22
|
+
if (!incoming)
|
|
23
|
+
return existing.workspaces;
|
|
24
|
+
return incoming.map((newWs) => {
|
|
25
|
+
const existingWs = existing.workspaces?.find((w) => w.id === newWs.id);
|
|
26
|
+
return existingWs
|
|
27
|
+
? {
|
|
28
|
+
...newWs,
|
|
29
|
+
enabled: existingWs.enabled,
|
|
30
|
+
disabledAt: existingWs.disabledAt,
|
|
31
|
+
}
|
|
32
|
+
: newWs;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the workspace index a refreshed account should point at after a
|
|
37
|
+
* merge: keep the user on their current workspace if it survived, else the
|
|
38
|
+
* default workspace, else the first enabled one, else index 0.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveCurrentWorkspaceIndex(existing, mergedWorkspaces) {
|
|
41
|
+
if (!mergedWorkspaces || mergedWorkspaces.length === 0) {
|
|
42
|
+
return existing.currentWorkspaceIndex;
|
|
43
|
+
}
|
|
44
|
+
const currentWorkspaceId = existing.workspaces?.[typeof existing.currentWorkspaceIndex === "number"
|
|
45
|
+
? existing.currentWorkspaceIndex
|
|
46
|
+
: 0]?.id;
|
|
47
|
+
if (currentWorkspaceId) {
|
|
48
|
+
const matching = mergedWorkspaces.findIndex((workspace) => workspace.id === currentWorkspaceId);
|
|
49
|
+
if (matching >= 0)
|
|
50
|
+
return matching;
|
|
51
|
+
}
|
|
52
|
+
const defaultIndex = mergedWorkspaces.findIndex((workspace) => workspace.isDefault === true);
|
|
53
|
+
if (defaultIndex >= 0)
|
|
54
|
+
return defaultIndex;
|
|
55
|
+
const firstEnabled = mergedWorkspaces.findIndex((workspace) => workspace.enabled !== false);
|
|
56
|
+
return firstEnabled >= 0 ? firstEnabled : 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build the saved record for a login result that did not match any existing
|
|
60
|
+
* entry, seeding its workspace tracking from the token-derived workspaces.
|
|
61
|
+
*/
|
|
62
|
+
export function buildInsertedAccount(write) {
|
|
63
|
+
const initialWorkspaceIndex = write.workspaces && write.workspaces.length > 0
|
|
64
|
+
? pickInitialWorkspaceIndex(write.workspaces, write.accountId)
|
|
65
|
+
: undefined;
|
|
66
|
+
return {
|
|
67
|
+
outcome: "inserted",
|
|
68
|
+
account: {
|
|
69
|
+
accountId: write.accountId,
|
|
70
|
+
accountIdSource: write.accountIdSource,
|
|
71
|
+
accountLabel: write.accountLabel,
|
|
72
|
+
email: write.email,
|
|
73
|
+
refreshToken: write.refreshToken,
|
|
74
|
+
accessToken: write.accessToken,
|
|
75
|
+
expiresAt: write.expiresAt,
|
|
76
|
+
enabled: true,
|
|
77
|
+
addedAt: write.now,
|
|
78
|
+
lastUsed: write.now,
|
|
79
|
+
workspaces: write.workspaces,
|
|
80
|
+
currentWorkspaceIndex: initialWorkspaceIndex,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Fold a login result onto an existing saved entry: refresh tokens/identity,
|
|
86
|
+
* merge workspace tracking, and classify whether this was a plain `updated`
|
|
87
|
+
* refresh or a `rebound` (a previously-unknown workspace appeared).
|
|
88
|
+
*/
|
|
89
|
+
export function buildUpdatedAccount(existing, write) {
|
|
90
|
+
const nextEmail = write.email ?? existing.email;
|
|
91
|
+
const nextAccountId = write.accountId ?? existing.accountId;
|
|
92
|
+
const nextAccountIdSource = write.accountId
|
|
93
|
+
? (write.accountIdSource ?? existing.accountIdSource)
|
|
94
|
+
: existing.accountIdSource;
|
|
95
|
+
const previousWorkspaceIds = new Set((existing.workspaces ?? []).map((workspace) => workspace.id));
|
|
96
|
+
const mergedWorkspaces = mergeAccountWorkspaces(existing, write.workspaces);
|
|
97
|
+
// Only a genuine *rebind* counts as "rebound": the account already tracked
|
|
98
|
+
// workspaces and the login surfaced one it had never seen. First-time
|
|
99
|
+
// enrichment of a pre-#491 row (no prior workspaces) is a plain `updated`,
|
|
100
|
+
// so the user is not told "Rebound workspace" on their first workspace-aware
|
|
101
|
+
// re-login (#512 follow-up).
|
|
102
|
+
const introducedNewWorkspace = previousWorkspaceIds.size > 0 &&
|
|
103
|
+
Boolean(write.workspaces?.some((workspace) => !previousWorkspaceIds.has(workspace.id)));
|
|
104
|
+
const nextCurrentWorkspaceIndex = resolveCurrentWorkspaceIndex(existing, mergedWorkspaces);
|
|
105
|
+
return {
|
|
106
|
+
outcome: introducedNewWorkspace ? "rebound" : "updated",
|
|
107
|
+
account: {
|
|
108
|
+
...existing,
|
|
109
|
+
accountId: nextAccountId,
|
|
110
|
+
accountIdSource: nextAccountIdSource,
|
|
111
|
+
accountLabel: write.accountLabel ?? existing.accountLabel,
|
|
112
|
+
email: nextEmail,
|
|
113
|
+
refreshToken: write.refreshToken,
|
|
114
|
+
accessToken: write.accessToken,
|
|
115
|
+
expiresAt: write.expiresAt,
|
|
116
|
+
enabled: true,
|
|
117
|
+
lastUsed: write.now,
|
|
118
|
+
workspaces: mergedWorkspaces,
|
|
119
|
+
currentWorkspaceIndex: nextCurrentWorkspaceIndex,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Fold a batch of resolved login writes into the saved account list, returning
|
|
125
|
+
* the next account array, the selected index, and the outcome of the last
|
|
126
|
+
* write. This is the pure core of `persistAccountPool`: it owns the dedup →
|
|
127
|
+
* insert/update → workspace-tracking → active-index decisions that issue #512
|
|
128
|
+
* depends on, with the matching strategy injected so the production path and
|
|
129
|
+
* tests share the exact same behaviour.
|
|
130
|
+
*/
|
|
131
|
+
export function applyAccountPoolResults(params) {
|
|
132
|
+
const accounts = [...params.existing];
|
|
133
|
+
let selectedAccountIndex = null;
|
|
134
|
+
let selectedOutcome = null;
|
|
135
|
+
for (const write of params.writes) {
|
|
136
|
+
const existingIndex = params.findMatchingAccountIndex(accounts, {
|
|
137
|
+
accountId: write.accountId,
|
|
138
|
+
email: write.email,
|
|
139
|
+
refreshToken: write.refreshToken,
|
|
140
|
+
}, { allowUniqueAccountIdFallbackWithoutEmail: true });
|
|
141
|
+
if (existingIndex === undefined) {
|
|
142
|
+
const { account, outcome } = buildInsertedAccount(write);
|
|
143
|
+
selectedAccountIndex = accounts.length;
|
|
144
|
+
accounts.push(account);
|
|
145
|
+
selectedOutcome = outcome;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const existing = accounts[existingIndex];
|
|
149
|
+
if (!existing)
|
|
150
|
+
continue;
|
|
151
|
+
const { account, outcome } = buildUpdatedAccount(existing, write);
|
|
152
|
+
accounts[existingIndex] = account;
|
|
153
|
+
selectedAccountIndex = existingIndex;
|
|
154
|
+
selectedOutcome = outcome;
|
|
155
|
+
}
|
|
156
|
+
const fallbackActiveIndex = accounts.length === 0
|
|
157
|
+
? 0
|
|
158
|
+
: Math.max(0, Math.min(params.priorActiveIndex ?? 0, accounts.length - 1));
|
|
159
|
+
const activeIndex = accounts.length === 0
|
|
160
|
+
? 0
|
|
161
|
+
: selectedAccountIndex === null
|
|
162
|
+
? fallbackActiveIndex
|
|
163
|
+
: Math.max(0, Math.min(selectedAccountIndex, accounts.length - 1));
|
|
164
|
+
return { accounts, activeIndex, outcome: selectedOutcome };
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=account-pool-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-pool-write.js","sourceRoot":"","sources":["../../../lib/codex-manager/account-pool-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classification of a manual OAuth callback entry.
|
|
3
|
+
*
|
|
4
|
+
* Splitting `invalid` and `state-mismatch` out from `cancelled` is the fix for
|
|
5
|
+
* the issue #512 follow-up: `login --manual` previously collapsed a malformed
|
|
6
|
+
* or wrong-attempt callback URL into the same `null` it used for a genuine user
|
|
7
|
+
* abort, so the CLI printed `Cancelled.` and hid the real validation error.
|
|
8
|
+
*
|
|
9
|
+
* - `code`: a valid authorization code + matching state were extracted.
|
|
10
|
+
* - `cancelled`: the user aborted (empty input, an Esc character, or one of the
|
|
11
|
+
* cancel keywords `q`/`quit`/`cancel`/`back`).
|
|
12
|
+
* - `invalid`: the pasted value was missing the code and/or state parameter.
|
|
13
|
+
* - `state-mismatch`: the state did not match the one minted for this attempt.
|
|
14
|
+
*/
|
|
15
|
+
export type ManualCallbackClassification = {
|
|
16
|
+
type: "code";
|
|
17
|
+
code: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: "cancelled";
|
|
20
|
+
} | {
|
|
21
|
+
type: "invalid";
|
|
22
|
+
} | {
|
|
23
|
+
type: "state-mismatch";
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Classify raw manual-callback input against the expected OAuth state. Pure and
|
|
27
|
+
* I/O-free so the CLI prompt and unit tests share identical behaviour.
|
|
28
|
+
*
|
|
29
|
+
* `answer` is `null` when the input stream closed before any line was read,
|
|
30
|
+
* which is treated as a cancellation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function classifyManualCallbackInput(answer: string | null, expectedState: string): ManualCallbackClassification;
|
|
33
|
+
//# sourceMappingURL=manual-callback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual-callback.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/manual-callback.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,4BAA4B,GACrC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAK9B;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAC1C,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,aAAa,EAAE,MAAM,GACnB,4BAA4B,CAmB9B"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseAuthorizationInput } from "../auth/auth.js";
|
|
2
|
+
const CANCEL_KEYWORDS = new Set(["", "q", "quit", "cancel", "back"]);
|
|
3
|
+
const ESC_CHARACTER = "\u001b";
|
|
4
|
+
/**
|
|
5
|
+
* Classify raw manual-callback input against the expected OAuth state. Pure and
|
|
6
|
+
* I/O-free so the CLI prompt and unit tests share identical behaviour.
|
|
7
|
+
*
|
|
8
|
+
* `answer` is `null` when the input stream closed before any line was read,
|
|
9
|
+
* which is treated as a cancellation.
|
|
10
|
+
*/
|
|
11
|
+
export function classifyManualCallbackInput(answer, expectedState) {
|
|
12
|
+
if (answer === null) {
|
|
13
|
+
return { type: "cancelled" };
|
|
14
|
+
}
|
|
15
|
+
if (answer.includes(ESC_CHARACTER)) {
|
|
16
|
+
return { type: "cancelled" };
|
|
17
|
+
}
|
|
18
|
+
const normalized = answer.trim().toLowerCase();
|
|
19
|
+
if (CANCEL_KEYWORDS.has(normalized)) {
|
|
20
|
+
return { type: "cancelled" };
|
|
21
|
+
}
|
|
22
|
+
const parsed = parseAuthorizationInput(answer);
|
|
23
|
+
if (!parsed.code || !parsed.state) {
|
|
24
|
+
return { type: "invalid" };
|
|
25
|
+
}
|
|
26
|
+
if (parsed.state !== expectedState) {
|
|
27
|
+
return { type: "state-mismatch" };
|
|
28
|
+
}
|
|
29
|
+
return { type: "code", code: parsed.code };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=manual-callback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manual-callback.js","sourceRoot":"","sources":["../../../lib/codex-manager/manual-callback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAsB1D,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AACrE,MAAM,aAAa,GAAG,QAAQ,CAAC;AAE/B;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAC1C,MAAqB,EACrB,aAAqB;IAErB,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC9B,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC9B,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/C,IAAI,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC9B,CAAC;IACD,MAAM,MAAM,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACnC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,aAAa,EAAE,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACnC,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-manager.d.ts","sourceRoot":"","sources":["../../lib/codex-manager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codex-manager.d.ts","sourceRoot":"","sources":["../../lib/codex-manager.ts"],"names":[],"mappings":"AAwNA,KAAK,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AA8MxE,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,MAAM,EACd,YAAY,GAAE,UAAoB,GAChC,MAAM,CA4CR;AAuhCD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ3D;AAg3DD,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC,CAuGrE;AAuCD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA+O7E"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from "node:process";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { extractAccountEmail, extractAccountId, formatAccountLabel, formatWaitTime, getAccountIdCandidates, resolveRequestAccountId, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, } from "./accounts.js";
|
|
4
|
-
import { createAuthorizationFlow, exchangeAuthorizationCode,
|
|
4
|
+
import { createAuthorizationFlow, exchangeAuthorizationCode, redactOAuthUrlForLog, REDIRECT_URI, } from "./auth/auth.js";
|
|
5
5
|
import { runDeviceAuthFlow } from "./auth/device-auth.js";
|
|
6
6
|
import { resolveOrgOverride } from "./auth/org-override.js";
|
|
7
7
|
import { copyTextToClipboard, isBrowserLaunchSuppressed, openBrowserUrl, } from "./auth/browser.js";
|
|
@@ -12,6 +12,8 @@ import { setCodexCliActiveSelection } from "./codex-cli/writer.js";
|
|
|
12
12
|
import { runBestCommand, } from "./codex-manager/commands/best.js";
|
|
13
13
|
import { runAccountCommand } from "./codex-manager/commands/account.js";
|
|
14
14
|
import { ACCOUNT_MANAGER_COMMANDS } from "./codex-manager/account-manager-commands.js";
|
|
15
|
+
import { applyAccountPoolResults, } from "./codex-manager/account-pool-write.js";
|
|
16
|
+
import { classifyManualCallbackInput, } from "./codex-manager/manual-callback.js";
|
|
15
17
|
import { runBudgetCommand } from "./codex-manager/commands/budget.js";
|
|
16
18
|
import { runBridgeCommand } from "./codex-manager/commands/bridge.js";
|
|
17
19
|
import { runCheckCommand } from "./codex-manager/commands/check.js";
|
|
@@ -856,15 +858,33 @@ async function syncCodexCliActiveSelectionIfDrifted(storage) {
|
|
|
856
858
|
* process.env for the duration of a login, which raced on concurrent re-entry.
|
|
857
859
|
*/
|
|
858
860
|
function resolveAccountSelection(tokens, orgOverride) {
|
|
861
|
+
const candidates = getAccountIdCandidates(tokens.access, tokens.idToken);
|
|
862
|
+
// Surface every workspace/organization exposed by the token so the saved
|
|
863
|
+
// account can track them (issue #491/#512). Without this, same-email
|
|
864
|
+
// multi-workspace logins persisted rows with `workspaces: null` and
|
|
865
|
+
// `workspace <account>` was unusable. Built before the `--org` override
|
|
866
|
+
// branch so the explicit-binding flow persists workspaces too (#512).
|
|
867
|
+
const workspaces = candidates.length > 0
|
|
868
|
+
? candidates.map((candidate) => ({
|
|
869
|
+
id: candidate.accountId,
|
|
870
|
+
name: candidate.label,
|
|
871
|
+
enabled: true,
|
|
872
|
+
isDefault: candidate.isDefault,
|
|
873
|
+
}))
|
|
874
|
+
: undefined;
|
|
859
875
|
const override = resolveOrgOverride(orgOverride);
|
|
860
876
|
if (override) {
|
|
877
|
+
// Prefer the token candidate's human label for the chosen org so the
|
|
878
|
+
// saved row is identifiable, falling back to a bare manual binding.
|
|
879
|
+
const matched = candidates.find((candidate) => candidate.accountId === override);
|
|
861
880
|
return {
|
|
862
881
|
...tokens,
|
|
863
882
|
accountIdOverride: override,
|
|
864
883
|
accountIdSource: "manual",
|
|
884
|
+
accountLabel: matched?.label,
|
|
885
|
+
workspaces,
|
|
865
886
|
};
|
|
866
887
|
}
|
|
867
|
-
const candidates = getAccountIdCandidates(tokens.access, tokens.idToken);
|
|
868
888
|
if (candidates.length === 0) {
|
|
869
889
|
return tokens;
|
|
870
890
|
}
|
|
@@ -876,6 +896,7 @@ function resolveAccountSelection(tokens, orgOverride) {
|
|
|
876
896
|
accountIdOverride: candidate.accountId,
|
|
877
897
|
accountIdSource: candidate.source,
|
|
878
898
|
accountLabel: candidate.label,
|
|
899
|
+
workspaces,
|
|
879
900
|
};
|
|
880
901
|
}
|
|
881
902
|
}
|
|
@@ -888,6 +909,7 @@ function resolveAccountSelection(tokens, orgOverride) {
|
|
|
888
909
|
accountIdOverride: best.accountId,
|
|
889
910
|
accountIdSource: best.source ?? "token",
|
|
890
911
|
accountLabel: best.label,
|
|
912
|
+
workspaces,
|
|
891
913
|
};
|
|
892
914
|
}
|
|
893
915
|
function resolveStoredAccountIdentity(storedAccountId, storedAccountIdSource, tokenAccountId) {
|
|
@@ -922,7 +944,7 @@ function applyTokenAccountIdentity(account, tokenAccountId) {
|
|
|
922
944
|
async function promptManualCallback(state, options = {}) {
|
|
923
945
|
const useInteractivePrompt = input.isTTY && output.isTTY;
|
|
924
946
|
if (!useInteractivePrompt && !options.allowNonTty) {
|
|
925
|
-
return
|
|
947
|
+
return { type: "cancelled" };
|
|
926
948
|
}
|
|
927
949
|
const rl = createInterface({ input, output });
|
|
928
950
|
try {
|
|
@@ -974,30 +996,11 @@ async function promptManualCallback(state, options = {}) {
|
|
|
974
996
|
input.once("end", handleInputClosed);
|
|
975
997
|
input.once("close", handleInputClosed);
|
|
976
998
|
});
|
|
977
|
-
|
|
978
|
-
return null;
|
|
979
|
-
}
|
|
980
|
-
if (answer.includes("\u001b")) {
|
|
981
|
-
return null;
|
|
982
|
-
}
|
|
983
|
-
const normalized = answer.trim().toLowerCase();
|
|
984
|
-
if (normalized.length === 0 ||
|
|
985
|
-
normalized === "q" ||
|
|
986
|
-
normalized === "quit" ||
|
|
987
|
-
normalized === "cancel" ||
|
|
988
|
-
normalized === "back") {
|
|
989
|
-
return null;
|
|
990
|
-
}
|
|
991
|
-
const parsed = parseAuthorizationInput(answer);
|
|
992
|
-
if (!parsed.code || !parsed.state)
|
|
993
|
-
return null;
|
|
994
|
-
if (parsed.state !== state)
|
|
995
|
-
return null;
|
|
996
|
-
return parsed.code;
|
|
999
|
+
return classifyManualCallbackInput(answer, state);
|
|
997
1000
|
}
|
|
998
1001
|
catch (error) {
|
|
999
1002
|
if (isAbortError(error) || isReadlineClosedError(error)) {
|
|
1000
|
-
return
|
|
1003
|
+
return { type: "cancelled" };
|
|
1001
1004
|
}
|
|
1002
1005
|
throw error;
|
|
1003
1006
|
}
|
|
@@ -1455,9 +1458,28 @@ async function runOAuthFlow(forceNewLogin, signInMode) {
|
|
|
1455
1458
|
: signInMode === "manual"
|
|
1456
1459
|
? UI_COPY.oauth.callbackBypassed
|
|
1457
1460
|
: UI_COPY.oauth.callbackUnavailable, "warning"));
|
|
1458
|
-
|
|
1461
|
+
const manualResult = await promptManualCallback(state, {
|
|
1459
1462
|
allowNonTty: signInMode === "manual",
|
|
1460
1463
|
});
|
|
1464
|
+
// A parse/state failure must surface its own validation error instead
|
|
1465
|
+
// of being reported as `Cancelled.` like a genuine user abort
|
|
1466
|
+
// (issue #512 follow-up). Only an actual cancellation falls through to
|
|
1467
|
+
// the cancelled path below.
|
|
1468
|
+
if (manualResult.type === "invalid") {
|
|
1469
|
+
return {
|
|
1470
|
+
type: "failed",
|
|
1471
|
+
reason: "invalid_response",
|
|
1472
|
+
message: UI_COPY.oauth.callbackInvalid,
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
if (manualResult.type === "state-mismatch") {
|
|
1476
|
+
return {
|
|
1477
|
+
type: "failed",
|
|
1478
|
+
reason: "invalid_response",
|
|
1479
|
+
message: UI_COPY.oauth.callbackStateMismatch,
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
code = manualResult.type === "code" ? manualResult.code : null;
|
|
1461
1483
|
}
|
|
1462
1484
|
}
|
|
1463
1485
|
finally {
|
|
@@ -1490,85 +1512,47 @@ async function runSignInFlow(forceNewLogin, signInMode, options = {}) {
|
|
|
1490
1512
|
}
|
|
1491
1513
|
async function persistAccountPool(results, replaceAll) {
|
|
1492
1514
|
if (results.length === 0)
|
|
1493
|
-
return;
|
|
1494
|
-
await withAccountStorageTransaction(async (loadedStorage, persist) => {
|
|
1515
|
+
return null;
|
|
1516
|
+
return await withAccountStorageTransaction(async (loadedStorage, persist) => {
|
|
1495
1517
|
const stored = replaceAll ? null : loadedStorage;
|
|
1496
1518
|
const now = Date.now();
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
for (const result of results) {
|
|
1519
|
+
const existing = stored?.accounts ? [...stored.accounts] : [];
|
|
1520
|
+
const writes = results.map((result) => {
|
|
1500
1521
|
const tokenAccountId = extractAccountId(result.access);
|
|
1501
1522
|
const accountId = resolveRequestAccountId(result.accountIdOverride, result.accountIdSource, tokenAccountId);
|
|
1502
1523
|
const accountIdSource = accountId
|
|
1503
1524
|
? (result.accountIdSource ??
|
|
1504
1525
|
(result.accountIdOverride ? "manual" : "token"))
|
|
1505
1526
|
: undefined;
|
|
1506
|
-
|
|
1507
|
-
const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
|
|
1508
|
-
const existingIndex = findMatchingAccountIndex(accounts, {
|
|
1527
|
+
return {
|
|
1509
1528
|
accountId,
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
allowUniqueAccountIdFallbackWithoutEmail: true,
|
|
1514
|
-
});
|
|
1515
|
-
if (existingIndex === undefined) {
|
|
1516
|
-
const newIndex = accounts.length;
|
|
1517
|
-
accounts.push({
|
|
1518
|
-
accountId,
|
|
1519
|
-
accountIdSource,
|
|
1520
|
-
accountLabel,
|
|
1521
|
-
email: accountEmail,
|
|
1522
|
-
refreshToken: result.refresh,
|
|
1523
|
-
accessToken: result.access,
|
|
1524
|
-
expiresAt: result.expires,
|
|
1525
|
-
enabled: true,
|
|
1526
|
-
addedAt: now,
|
|
1527
|
-
lastUsed: now,
|
|
1528
|
-
});
|
|
1529
|
-
selectedAccountIndex = newIndex;
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
const existing = accounts[existingIndex];
|
|
1533
|
-
if (!existing)
|
|
1534
|
-
continue;
|
|
1535
|
-
const nextEmail = accountEmail ?? existing.email;
|
|
1536
|
-
const nextAccountId = accountId ?? existing.accountId;
|
|
1537
|
-
const nextAccountIdSource = accountId
|
|
1538
|
-
? (accountIdSource ?? existing.accountIdSource)
|
|
1539
|
-
: existing.accountIdSource;
|
|
1540
|
-
accounts[existingIndex] = {
|
|
1541
|
-
...existing,
|
|
1542
|
-
accountId: nextAccountId,
|
|
1543
|
-
accountIdSource: nextAccountIdSource,
|
|
1544
|
-
accountLabel: accountLabel ?? existing.accountLabel,
|
|
1545
|
-
email: nextEmail,
|
|
1529
|
+
accountIdSource,
|
|
1530
|
+
accountLabel: result.accountLabel,
|
|
1531
|
+
email: sanitizeEmail(extractAccountEmail(result.access, result.idToken)),
|
|
1546
1532
|
refreshToken: result.refresh,
|
|
1547
1533
|
accessToken: result.access,
|
|
1548
1534
|
expiresAt: result.expires,
|
|
1549
|
-
|
|
1550
|
-
|
|
1535
|
+
workspaces: result.workspaces,
|
|
1536
|
+
now,
|
|
1551
1537
|
};
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
:
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
: selectedAccountIndex === null
|
|
1560
|
-
? fallbackActiveIndex
|
|
1561
|
-
: Math.max(0, Math.min(selectedAccountIndex, accounts.length - 1));
|
|
1538
|
+
});
|
|
1539
|
+
const { accounts, activeIndex, outcome } = applyAccountPoolResults({
|
|
1540
|
+
existing,
|
|
1541
|
+
writes,
|
|
1542
|
+
priorActiveIndex: stored?.activeIndex,
|
|
1543
|
+
findMatchingAccountIndex,
|
|
1544
|
+
});
|
|
1562
1545
|
const activeIndexByFamily = {};
|
|
1563
1546
|
for (const family of MODEL_FAMILIES) {
|
|
1564
|
-
activeIndexByFamily[family] =
|
|
1547
|
+
activeIndexByFamily[family] = activeIndex;
|
|
1565
1548
|
}
|
|
1566
1549
|
await persist({
|
|
1567
1550
|
version: 3,
|
|
1568
1551
|
accounts,
|
|
1569
|
-
activeIndex
|
|
1552
|
+
activeIndex,
|
|
1570
1553
|
activeIndexByFamily,
|
|
1571
1554
|
});
|
|
1555
|
+
return outcome;
|
|
1572
1556
|
});
|
|
1573
1557
|
}
|
|
1574
1558
|
async function syncSelectionToCodex(tokens) {
|
|
@@ -2396,14 +2380,22 @@ async function runAuthLoginFlow(loginOptions) {
|
|
|
2396
2380
|
return 1;
|
|
2397
2381
|
}
|
|
2398
2382
|
const resolved = resolveAccountSelection(tokenResult, loginOptions.org);
|
|
2399
|
-
await persistAccountPool([resolved], false);
|
|
2383
|
+
const persistOutcome = await persistAccountPool([resolved], false);
|
|
2400
2384
|
await syncSelectionToCodex(resolved);
|
|
2401
2385
|
const latestStorage = await loadAccounts();
|
|
2402
2386
|
const count = latestStorage?.accounts.length ?? 1;
|
|
2403
2387
|
existingCount = count;
|
|
2404
2388
|
namedBackups = [];
|
|
2405
2389
|
onboardingBackupDiscoveryWarning = null;
|
|
2406
|
-
|
|
2390
|
+
// Only claim a new saved slot when one was actually appended. A
|
|
2391
|
+
// same-email login that maps onto an existing entry updates or
|
|
2392
|
+
// rebinds it instead of growing the pool (issue #512).
|
|
2393
|
+
const outcomeMessage = persistOutcome === "rebound"
|
|
2394
|
+
? `Rebound workspace for existing account. Total: ${count}`
|
|
2395
|
+
: persistOutcome === "updated"
|
|
2396
|
+
? `Updated existing account. Total: ${count}`
|
|
2397
|
+
: `Added account. Total: ${count}`;
|
|
2398
|
+
console.log(outcomeMessage);
|
|
2407
2399
|
console.log("Next steps:");
|
|
2408
2400
|
console.log(" codex-multi-auth status Check that the wrapper is active.");
|
|
2409
2401
|
console.log(" codex-multi-auth check Confirm your saved accounts look healthy.");
|