mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
fix(oauth): avoid bare-email dedup of Codex OAuth logins (#6706)
* fix(oauth): avoid bare-email dedup of Codex OAuth logins When an incoming Codex OAuth connection has no verifiable workspace/account id, do not merge it into an existing row on email match alone — that silently overwrote the other account's token pair. Require a matching chatgptUserId (a stable per-account JWT id) before merging; otherwise insert a distinct connection row. Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com> Inspired-by: https://github.com/decolua/9router/pull/2477 * chore(6706): re-sync onto release tip; CHANGELOG → changelog.d fragment (fragments-first) --------- Co-authored-by: lucasjustinudin <34107354+lucasjustinudin@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b45d10ceea
commit
82f78320e8
1
changelog.d/fixes/6706-codex-oauth-bare-email-dedup.md
Normal file
1
changelog.d/fixes/6706-codex-oauth-bare-email-dedup.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(oauth):** stop merging distinct Codex OAuth logins that share an email but lack a verifiable account id, preventing silent token overwrite. (thanks @lucasjustinudin)
|
||||
@@ -169,6 +169,25 @@ export async function createProviderConnection(data: JsonRecord) {
|
||||
}
|
||||
// For Codex with workspaceId, don't fall back to email-only check
|
||||
// This allows creating new connections for different workspaces
|
||||
} else if (data.provider === "codex") {
|
||||
// Codex without a workspaceId — do NOT fall through to the generic
|
||||
// bare-email dedup below. Codex never sets providerSpecificData.username,
|
||||
// so that path's disambiguation is a no-op and two distinct Codex logins
|
||||
// sharing an email (but missing a verifiable workspace/account id) would
|
||||
// silently collapse into one row, overwriting the first login's token
|
||||
// pair. Require a matching chatgptUserId (a stable per-account id from
|
||||
// the JWT) before merging; otherwise treat this as a new connection.
|
||||
const chatgptUserId = toStringOrNull(providerSpecificData.chatgptUserId);
|
||||
if (chatgptUserId) {
|
||||
existing =
|
||||
(db
|
||||
.prepare(
|
||||
"SELECT * FROM provider_connections WHERE provider = ? AND auth_type = 'oauth' AND json_extract(provider_specific_data, '$.chatgptUserId') = ? AND email = ?"
|
||||
)
|
||||
.get(data.provider, chatgptUserId, data.email) as JsonRecord | undefined) || null;
|
||||
}
|
||||
// No chatgptUserId on the incoming row (or no existing match) — leave
|
||||
// `existing` null so a new connection row is inserted.
|
||||
} else {
|
||||
// For other providers (or Codex without workspaceId), match on email —
|
||||
// disambiguated by providerSpecificData.username when present on both
|
||||
|
||||
@@ -142,6 +142,37 @@ test("codex workspace uniqueness uses workspaceId alongside email", async () =>
|
||||
]);
|
||||
});
|
||||
|
||||
test("codex logins without a workspaceId are not merged on bare email match", async () => {
|
||||
const loginA = await providersDb.createProviderConnection({
|
||||
provider: "codex",
|
||||
authType: "oauth",
|
||||
email: "shared@example.com",
|
||||
accessToken: "token-account-a",
|
||||
refreshToken: "refresh-account-a",
|
||||
providerSpecificData: { chatgptUserId: "user-a" },
|
||||
});
|
||||
const loginB = await providersDb.createProviderConnection({
|
||||
provider: "codex",
|
||||
authType: "oauth",
|
||||
email: "shared@example.com",
|
||||
accessToken: "token-account-b",
|
||||
refreshToken: "refresh-account-b",
|
||||
providerSpecificData: { chatgptUserId: "user-b" },
|
||||
});
|
||||
|
||||
const rows = await providersDb.getProviderConnections({ provider: "codex" });
|
||||
|
||||
// Two distinct Codex accounts sharing an email but lacking a verifiable
|
||||
// workspaceId must NOT collapse into a single row — that would silently
|
||||
// overwrite the first account's token pair on the second login.
|
||||
assert.notEqual(loginB.id, loginA.id);
|
||||
assert.equal(rows.length, 2);
|
||||
|
||||
const rowA = rows.find((row) => row.id === loginA.id);
|
||||
assert.equal(rowA?.accessToken, "token-account-a");
|
||||
assert.equal(rowA?.refreshToken, "refresh-account-a");
|
||||
});
|
||||
|
||||
test("updateProviderConnection reorders priorities and returns decrypted payloads", async () => {
|
||||
const first = await providersDb.createProviderConnection({
|
||||
provider: "openai",
|
||||
|
||||
Reference in New Issue
Block a user