Skip to content

feat(developer): require a verified GitHub account to create an app - #945

Merged
vladjerca merged 1 commit into
masterfrom
feat/github-handle-portal
Sep 23, 2026
Merged

vladjerca merged 1 commit into
masterfrom
feat/github-handle-portal

Conversation

@vladjerca

@vladjerca vladjerca commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Summary

The developer portal side of requiring a verified GitHub account to create an API app. The VIP restriction on creating apps is removed at the same time.

The portal is its own OAuth redirect target. Clicking Connect GitHub stashes { state, returnPath } in sessionStorage and navigates to GitHub's authorize URL with one fixed callback of ${location.origin}/apps. GitHub bounces back to /apps?code=...&state=...; that page validates state, stashes the code, and returns to the form it came from. The form then submits the code in the body it was already sending, and the API exchanges it.

The portal never calls github.com itself, it only navigates there, so no CSP change is needed.

What changed

  • githubConnect.ts, new: builds the authorize URL, stashes state, the return path and the in-progress form draft, validates on return, ignores unknown params. Returns connected / denied / invalid so a cancelled authorization is distinguishable from a tampered one. The return path goes through the portal's existing safeReturnPath allowlist.
  • routes/(portal)/apps/+page.svelte: handles the callback, validates state, bounces back to the return path. An error callback (the user pressing Cancel on GitHub) clears the pending state and renders a cancelled message instead of falling through to the app list.
  • ApplicationForm.svelte: Connect GitHub on both create and edit. Unsaved form input survives the redirect, restored from the stashed draft when the form remounts with a code. Required on create only when the account has no linked GitHub identity yet. Once connected, further apps need no GitHub round trip, and the block shows the handle with a re-verify option instead.
  • Applications.svelte: derives the account's linked handle from the apps list and passes it to the form, so a returning developer is not asked to reconnect.
  • validateApplication.ts, applications.ts: carry github_code into the request, add github_username to the response schema.
  • accountRequest.ts: the 403 copy no longer mentions VIP, plus a real message for a stale or reused code.
  • Applications.svelte and ApplicationPage.svelte: VIP gating removed, along with the vip prop it orphaned.
  • create-an-app.md: states the requirement.
  • .env.example: PUBLIC_GITHUB_CLIENT_ID, read the same way PUBLIC_TRAKT_CLIENT_ID already is.

The Connect GitHub block is on the edit form deliberately. Existing apps have no handle, and deleting and recreating is not an option because a new app means a new client_id, which breaks every user already connected to it.

Depends on

The API-side change that adds github_code on the request and github_username on the response, and makes the code required only on the first link. That side is merged-ready and has been verified live against production.

Checks

  • deno fmt --check --config deno.json passes
  • deno task format:svelte:check passes
  • deno task check: 363 files, 0 errors, 0 warnings
  • deno task test: 176 tests passed

Not verified end to end

Local testing of the connect flow is not possible today, and a second GitHub OAuth App does not fix it.

src/lib/api/accountRequest.ts:14 hardcodes https://api.trakt.tv${path}, so a local portal always calls the production API, which holds the production GitHub client secret. A code issued to a local OAuth App cannot be exchanged by it. Using the production client id locally fails earlier still, because GitHub rejects a http://localhost:5174/apps redirect against a registration for https://developer.trakt.tv/apps.

So this is unit tested and will be smoke tested on production after deploy. The hardcoded API base is filed separately.

Note

PUBLIC_GITHUB_CLIENT_ID must be set in the deploy environment, or Connect GitHub builds an authorize URL with an empty client id. The workflow now passes it to the verify and deploy builds, and the repository variable is set.

@vladjerca
vladjerca force-pushed the feat/github-handle-portal branch 2 times, most recently from a0f2626 to 731c939 Compare September 22, 2026 13:09
@vladjerca
vladjerca marked this pull request as ready for review September 22, 2026 15:29
@vladjerca

Copy link
Copy Markdown
Contributor Author

Local end to end testing of this flow is blocked by the hardcoded API base. Tracked separately in #947.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 731c93953f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread projects/developer/src/lib/features/apps/githubConnect.ts
Comment thread projects/developer/src/lib/features/apps/ApplicationForm.svelte Outdated
Comment thread projects/developer/src/routes/(portal)/apps/+page.svelte Outdated
@vladjerca
vladjerca force-pushed the feat/github-handle-portal branch 2 times, most recently from 01f1954 to a30fc1c Compare September 22, 2026 18:13
@vladjerca

Copy link
Copy Markdown
Contributor Author

CI caught a gap I had missed: svelte-check types $env/static/public from the real environment, so PUBLIC_GITHUB_CLIENT_ID has to be declared in the workflow the same way PUBLIC_TRAKT_CLIENT_ID is. Added to verify, to the verify build, and to the deploy build.

Needs a repo variable before deploy: vars.PUBLIC_GITHUB_CLIENT_ID must be set to the production GitHub OAuth App client id, or the deployed portal builds an authorize URL with an empty client id.

Adds the Connect GitHub flow to the create and edit forms: the portal
stashes state and the return path, navigates to GitHub's authorize
URL with one fixed callback (/apps), and validates state on return
before carrying the code into the save request. Drops the VIP wall
from the portal, since the worker replaces it with a per-user app cap.

End-to-end verification is blocked on the trakt-workers PR that adds
github_code handling and github_username to the response.
@vladjerca
vladjerca force-pushed the feat/github-handle-portal branch from a30fc1c to 6108152 Compare September 23, 2026 08:11
@vladjerca

Copy link
Copy Markdown
Contributor Author

Addressed the codex review in 61081527:

  • P1 build env: already fixed in the commit that replaced the one under review, and the repository variable is set.
  • P2 lost form input: the draft is stashed next to the OAuth state and restored when the form remounts with a code.
  • P2 denial callbacks: an error callback is now detected, clears the pending state, and shows a cancelled message.

Also folded in a cleanup pass: the GitHub return path goes through the portal's existing safeReturnPath allowlist instead of being replayed straight from session storage.

deno fmt --check, format:svelte:check and deno task check all pass; 176 tests green.

@vladjerca
vladjerca merged commit 73455a7 into master Sep 23, 2026
5 checks passed
@vladjerca
vladjerca deleted the feat/github-handle-portal branch September 23, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant