Skip to content

fix(web): scope the ChatGPT BYOK connection to one account - #6317

Merged
iscekic merged 10 commits into
mainfrom
fix/chatgpt-byok-per-account
Sep 18, 2026
Merged

iscekic merged 10 commits into
mainfrom
fix/chatgpt-byok-per-account

Conversation

@iscekic

@iscekic iscekic commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

The "Sign in with ChatGPT" BYOK connection was personal only and shared everywhere. An organization request used the caller's personal connection, and the storage table allowed only one owner per row (person OR organization), so a member could not connect their own subscription to an organization.

Change

The connection is inherently personal. Each connection is keyed by (kilo_user_id, organization_id), where the organization is null for the personal account. The same person can connect the same ChatGPT subscription to several accounts by connecting each separately.

Account Connection used
Personal (user, null)
Org A, member connected (user, orgA)
Org A, another member that member's own row, never a teammate's
Org B, no connection gateway API pricing
  • New openai_chatgpt_connections table and migration. The shared byok_api_keys table is untouched.
  • store.ts, refresh.ts, and routing.ts resolve by the (member, account) pair.
  • openAiChatGpt.status / disconnect and linkAuthProvider require organization membership only: the connection is the member's own.
  • The organization BYOK page shows the card to every member; the pasted-key manager stays owner/admin.
  • Unlinking OpenAI clears the personal connection only; a member disconnects an organization connection from that organization's page.

Verification

  • Typecheck, lint, and 518 targeted tests pass (one unrelated softDeleteUser timing flake).
  • Live local e2e (real OpenAI OAuth on the registered localhost:7100 redirect, real DB):
    • Personal connect → inference reaches the ChatGPT subscription upstream (429 subscription_sharing_usage_limit_exceeded).
    • Connected the same subscription to org A → separate (user, orgA) row; the card shows connected.
    • Org B shows disconnected for the member while personal stays connected.
    • A second member of org A with no connection gets the API/credits path (402), never the first member's ChatGPT connection.

@iscekic iscekic self-assigned this Sep 18, 2026
@iscekic
iscekic requested a review from pandemicsyn September 18, 2026 16:11
Comment thread apps/web/src/lib/user/index.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The follow-up commit resolves the actionable findings from the prior review: anonymizeCloudUserData now deletes openai_chatgpt_connections, unlinkAuthProvider uses a provider-only input schema, and the OpenAI linking callback requires targetProvider === 'openai'. The incremental diff (5 files) introduces no new issues.

Files Reviewed (5 files)
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/lib/user/server.ts
  • apps/web/src/routers/user-router.ts
  • services/kilo-mcp/catalog.json
Previous Review Summaries (4 snapshots, latest commit f72dd37)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f72dd37)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The per-account ChatGPT connection scoping is sound, but the new openai_chatgpt_connections table is not included in the account soft-delete cleanup, and the reused link schema lets unlinkAuthProvider silently accept (and ignore) organizationId.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/db/src/schema.ts 6941 New ChatGPT credential table is missing from softDeleteUser/anonymizeCloudUserData, so the encrypted token and linked email survive account deletion
apps/web/src/routers/user-router.ts 143 unlinkAuthProvider reuses the link schema, so it accepts and ignores organizationId (now advertised in the MCP catalog) and unlinks the personal connection instead

SUGGESTION

File Line Issue
apps/web/src/lib/user/server.ts 941 Organization id is attached to any pending linking session, not only an OpenAI-targeted one
packages/db/src/migrations/0250_skinny_kate_bishop.sql 1 No backfill of existing byok_api_keys (openai-chatgpt) rows, so pre-existing connections are silently lost
Files Reviewed (37 files)
  • apps/web/src/app/(app)/components/OrganizationAppSidebar.tsx
  • apps/web/src/app/api/openrouter/models/route.test.ts
  • apps/web/src/app/api/openrouter/models/route.ts
  • apps/web/src/app/auth/openai/callback/route.test.ts
  • apps/web/src/app/auth/openai/callback/route.ts
  • apps/web/src/components/auth/SignInForm.signInOptions.test.ts
  • apps/web/src/components/auth/sign-in/EmailInputForm.tsx
  • apps/web/src/components/organizations/BYOKContent.tsx
  • apps/web/src/components/organizations/byok/OpenAiChatGptCard.test.ts
  • apps/web/src/components/organizations/byok/OpenAiChatGptCard.tsx
  • apps/web/src/lib/account-linking-session.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/owner-scope.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/refresh.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/routing.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts
  • apps/web/src/lib/ai-gateway/providers/get-provider.local-fake.test.ts
  • apps/web/src/lib/ai-gateway/providers/get-provider.ts
  • apps/web/src/lib/auth/provider-metadata.test.ts
  • apps/web/src/lib/auth/provider-metadata.tsx
  • apps/web/src/lib/organizations/organization-models.test.ts
  • apps/web/src/lib/organizations/organization-models.ts
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/lib/user/server-signin-callback.test.ts
  • apps/web/src/lib/user/server.test.ts
  • apps/web/src/lib/user/server.ts
  • apps/web/src/routers/openai-chatgpt-router.test.ts
  • apps/web/src/routers/openai-chatgpt-router.ts
  • apps/web/src/routers/user-router.ts
  • packages/db/src/migrations/0250_skinny_kate_bishop.sql
  • packages/db/src/migrations/meta/0250_snapshot.json
  • packages/db/src/migrations/meta/_journal.json
  • packages/db/src/schema.ts
  • services/kilo-mcp/catalog.json

Fix these issues in Kilo Cloud

Previous review (commit 61a6edc)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit reverts the organization-wide ChatGPT cleanup: unlinking a personal OpenAI identity now clears only the personal connection while an organization connection survives. The behavior is intentional, matches the updated code comment, and is covered by the updated tests, so no new issues were found in the changed code.

Files Reviewed (4 files)
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.test.ts
  • apps/web/src/lib/user/index.ts
  • apps/web/src/lib/user/index.test.ts

Previous review (commit 894ce46)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • apps/web/src/lib/ai-gateway/openai-chatgpt/owner-scope.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.test.ts
  • apps/web/src/lib/ai-gateway/openai-chatgpt/store.ts
  • apps/web/src/lib/user/index.test.ts
  • apps/web/src/lib/user/index.ts

Previous review (commit 97dbbae)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/user/index.ts 2337 Unlinking OpenAI clears only the personal connection; organization connections created by that person remain active
Files Reviewed (1 file)
  • apps/web/src/lib/ai-gateway/openai-chatgpt/owner-scope.test.ts

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 18, 2026
iscekic and others added 7 commits September 18, 2026 19:43
The "Sign in with ChatGPT" connection was personal only, so an
organization request used the caller's personal connection. Give each
account its own connection: an organization request resolves the
organization row and falls back to gateway pricing when the organization
has no connection.

- Store, refresh, and route the connection by owner (person or
  organization).
- Thread the organization through the OAuth linking session.
- Tag organization models from the organization connection.
- Render the card on the organization BYOK page.
Unlinking OpenAI cleared only the personal connection, so an organization
kept the delegated credential created from the detached identity. Clear
every `openai-chatgpt` row the person created, personal and organization,
and keep `created_by` current when a connection is replaced.
Unlinking the personal OpenAI identity must not delete an organization's
BYOK connection. The organization connection is a separate account setting
that an organization admin manages on the organization BYOK page. Clear
only the personal row and keep the organization row.
The "Sign in with ChatGPT" connection is inherently personal. Store each
connection in a dedicated `openai_chatgpt_connections` table keyed by
`(kilo_user_id, organization_id)`, where the organization is null for the
personal account. A member's connection for an organization is theirs
alone: another member's connection is never used, and an account without
one falls through to the API path.

- Add the table and migration.
- Resolve ChatGPT routing by the (member, account) pair.
- Any organization member can connect or disconnect their own.
- The organization BYOK page shows the card to every member; the
  pasted-key manager stays owner/admin only.
- Unlinking OpenAI clears the personal connection only.
Members see the BYOK nav item when the flag is on and only their own
ChatGPT card; the pasted-key manager stays owner/admin. The copy states
the connection is the member's own for the organization. The flag still
hides the option everywhere when it is off.
@iscekic
iscekic force-pushed the fix/chatgpt-byok-per-account branch from 4dde025 to 33c202f Compare September 18, 2026 17:45
Comment thread packages/db/src/schema.ts
Comment thread apps/web/src/routers/user-router.ts
Comment thread apps/web/src/lib/user/server.ts Outdated
Comment thread packages/db/src/migrations/0250_skinny_kate_bishop.sql
…ing scope

- Delete `openai_chatgpt_connections` rows in `anonymizeCloudUserData`, so a
  soft-deleted account does not retain the credential and linked email.
- Give `unlinkAuthProvider` its own input schema; it no longer advertises an
  `organizationId` it ignores.
- Attach the organization only when the linking session targeted OpenAI, and
  reject an organization-scoped link for any other provider.
- Regenerate the MCP catalog.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants