-
Notifications
You must be signed in to change notification settings - Fork 281
feat(accounts): export OpenCode auth json #532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: Build and publish Docker image | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| push: | ||
| branches: | ||
| - main | ||
| - feat/opencode-auth-export | ||
| tags: | ||
| - "v*" | ||
|
|
||
| permissions: | ||
| contents: read | ||
| packages: write | ||
|
|
||
| env: | ||
| IMAGE_NAME: ghcr.io/lnimien/codex-lb | ||
|
|
||
| jobs: | ||
| docker: | ||
| name: Docker image | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Log in to GitHub Container Registry | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| registry: ghcr.io | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Extract Docker metadata | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ${{ env.IMAGE_NAME }} | ||
| tags: | | ||
| type=raw,value=opencode-auth-export,enable=${{ github.ref == 'refs/heads/feat/opencode-auth-export' }} | ||
| type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} | ||
| type=ref,event=tag | ||
| type=sha,prefix=sha- | ||
|
|
||
| - name: Build and push Docker image | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| push: true | ||
| platforms: linux/amd64 | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| claims_from_auth, | ||
| generate_unique_account_id, | ||
| parse_auth_json, | ||
| token_expiry_epoch_ms, | ||
| ) | ||
| from app.core.auth.api_key_cache import get_api_key_cache | ||
| from app.core.cache.invalidation import NAMESPACE_API_KEY, get_cache_invalidation_poller | ||
|
|
@@ -25,9 +26,13 @@ | |
| AccountAdditionalQuota, | ||
| AccountAdditionalWindow, | ||
| AccountImportResponse, | ||
| AccountOpenCodeAuthExportAccount, | ||
| AccountOpenCodeAuthExportResponse, | ||
| AccountRequestUsage, | ||
| AccountSummary, | ||
| AccountTrendsResponse, | ||
| OpenCodeAuthJson, | ||
| OpenCodeOAuthAuth, | ||
| ) | ||
| from app.modules.proxy.account_cache import get_account_selection_cache | ||
| from app.modules.usage.additional_quota_keys import get_additional_display_label_for_quota_key | ||
|
|
@@ -142,6 +147,33 @@ async def get_account_trends(self, account_id: str) -> AccountTrendsResponse | N | |
| secondary=trend.secondary if trend else [], | ||
| ) | ||
|
|
||
| async def export_opencode_auth(self, account_id: str) -> AccountOpenCodeAuthExportResponse | None: | ||
| account = await self._repo.get_by_id(account_id) | ||
| if account is None: | ||
| return None | ||
|
|
||
| access_token = self._encryptor.decrypt(account.access_token_encrypted) | ||
| refresh_token = self._encryptor.decrypt(account.refresh_token_encrypted) | ||
| expires = token_expiry_epoch_ms(access_token) or 0 | ||
| opencode_account_id = account.chatgpt_account_id or account.id | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
|
|
||
| return AccountOpenCodeAuthExportResponse( | ||
| filename=_opencode_auth_export_filename(account), | ||
| account=AccountOpenCodeAuthExportAccount( | ||
| account_id=account.id, | ||
| chatgpt_account_id=account.chatgpt_account_id, | ||
| email=account.email, | ||
| ), | ||
| auth_json=OpenCodeAuthJson( | ||
| openai=OpenCodeOAuthAuth( | ||
| refresh=refresh_token, | ||
| access=access_token, | ||
| expires=expires, | ||
| account_id=opencode_account_id, | ||
| ), | ||
| ), | ||
| ) | ||
|
|
||
| async def import_account(self, raw: bytes) -> AccountImportResponse: | ||
| try: | ||
| auth = parse_auth_json(raw) | ||
|
|
@@ -201,3 +233,9 @@ async def delete_account(self, account_id: str) -> bool: | |
| if poller is not None: | ||
| await poller.bump(NAMESPACE_API_KEY) | ||
| return result | ||
|
|
||
|
|
||
| def _opencode_auth_export_filename(account: Account) -> str: | ||
| source = account.email or account.id | ||
| safe = "".join(char if char.isalnum() or char in "._-" else "-" for char in source).strip("-._") | ||
| return f"opencode-auth-{safe or account.id}.json" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Falling back to
account.idhere can generate anauthJson.openai.accountIdvalue that is not a real ChatGPT account ID (e.g. deduplicated IDs likeacc_xxx_<hash>or local fallback IDs). For accounts wherechatgpt_account_idis absent, this exports a misleadingaccountIdinstead of leaving it unset, and OpenCode will then send that invalid value asChatGPT-Account-Id, which can cause downstream auth/account selection failures for those exported credentials.Useful? React with 👍 / 👎.