mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
fix(ai): device-code flow uses OAuth access token directly (v1.4.8 follow-up)
v1.4.8's device-code flow got the user as far as the chatgpt.com approval, but the connect step at the end still failed with: Codex api-key exchange failed (401): "Invalid ID token: missing organization_id" Root cause: the api-key exchange (RFC 8693, swap id_token for an OpenAI API key) requires the id_token to carry an `organization_id` claim. That claim only gets added when the authorize URL is hit with `id_token_add_organizations=true` — which the browser/auth-code flow does, but the device-code flow does NOT (the user types the code on chatgpt.com's own page; we never control the authorize URL). The official Codex CLI handles this by NOT calling the api-key exchange in the device path: it persists the OAuth access_token / refresh_token directly and uses them against `https://chatgpt.com/backend-api/codex/responses` (the Codex backend that bills against the ChatGPT subscription). v1.4.9 does the same. `pollDeviceCode` no longer calls `obtainApiKey`; it returns the raw OAuth access_token instead. New `refreshDeviceTokens()` mirrors that on the refresh path. The provider rewires Codex back through the existing `CodexClient` (bg backend-api), with proactive refresh + on-401 refresh both going through `refreshDeviceTokens`. The browser/auth-code flow path is left in place — operators who register a private OAuth app with OpenAI (e.g. for an enterprise deployment) can still use the api-key exchange via the `exchangeCodeForTokens` helper. For the public Codex client ID on hosted instances, device-code is the only working path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
19eb1d95d6
commit
3dbc8af627
5 files changed
+129
-50
No files matched your search
@@ -1,7 +1,8 @@
|
||||
import { NextRequest } from "next/server";
|
||||
import { apiHandler, requireAuth, HttpError } from "@/lib/api-handler";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { pollDeviceCode, encryptCodexCreds } from "@/lib/ai/codex-oauth";
|
||||
import { pollDeviceCode } from "@/lib/ai/codex-oauth";
|
||||
import { encrypt } from "@/lib/crypto";
|
||||
import { cookies } from "next/headers";
|
||||
import { decrypt } from "@/lib/crypto";
|
||||
import { prisma } from "@/lib/db";
|
||||
@@ -67,16 +68,16 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
return apiSuccess({ status: "pending" });
|
||||
}
|
||||
|
||||
// status === "connected" → persist creds, audit, drop the cookie.
|
||||
const encrypted = encryptCodexCreds({
|
||||
apiKey: result.tokens.apiKey,
|
||||
refreshToken: result.tokens.refreshToken,
|
||||
});
|
||||
// status === "connected" → persist OAuth tokens directly. Device-
|
||||
// code flow uses the access_token against
|
||||
// https://chatgpt.com/backend-api/codex/responses — no api-key
|
||||
// exchange, because the id_token here lacks the organization claim
|
||||
// that exchange would require.
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
codexAccessTokenEncrypted: encrypted.apiKeyEncrypted,
|
||||
codexRefreshTokenEncrypted: encrypted.refreshEncrypted,
|
||||
codexAccessTokenEncrypted: encrypt(result.tokens.accessToken),
|
||||
codexRefreshTokenEncrypted: encrypt(result.tokens.refreshToken),
|
||||
codexTokenExpiresAt: result.tokens.expiresAt,
|
||||
codexConnectedAt: new Date(),
|
||||
codexConnectionStatus: "connected",
|
||||
|
||||
Reference in new issue
Block a user