Skip to content

feat(auth): GitHub OAuth login (opt-in) - #28

Merged
duyetbot merged 1 commit into
mainfrom
feat/github-oauth
Jul 3, 2026
Merged

feat(auth): GitHub OAuth login (opt-in)#28
duyetbot merged 1 commit into
mainfrom
feat/github-oauth

Conversation

@duyetbot

@duyetbot duyetbot commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

What / why

Adds GitHub as a better-auth social provider for the Console, mirroring
the existing Google OAuth wiring exactly — same opt-in-via-env pattern, same
runtime coverage (Cloudflare apps/main and Node self-host
apps/main-node), same Console button placement.

  • packages/auth-configbuildBetterAuth() accepts githubClientId /
    githubClientSecret; registers github on socialProviders only when
    both are set (else no-op, matches Google's behavior exactly).
  • apps/main/src/auth-config.ts + apps/main/src/index.ts (Cloudflare) —
    reads GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET from Env, advertises
    "github" on /auth-info when configured.
  • apps/main-node/src/index.ts (Node self-host, both Postgres and SQLite
    branches) — same env vars, same /auth-info advertisement.
  • packages/shared/src/env.ts — new GITHUB_CLIENT_ID?/GITHUB_CLIENT_SECRET?
    fields (deliberately distinct from the existing GITHUB_OAUTH_CLIENT_ID
    pair, which is unrelated — that one is for MCP-server/integration OAuth,
    not Console login).
  • apps/console/src/pages/Login.tsx — shows "Continue with GitHub" next to
    "Continue with Google" when /auth-info advertises the provider; wired
    via authClient.signIn.social({ provider: "github" }).
  • .env.example, docs/self-host.md, docs/deployment.md — document the
    new env vars and GitHub OAuth App setup steps.

When the env vars are unset, GitHub OAuth is simply not registered — zero
behavior change for existing deployments.

Env vars

Var Required Notes
GITHUB_CLIENT_ID No (opt-in) GitHub OAuth App client ID
GITHUB_CLIENT_SECRET No (opt-in) GitHub OAuth App client secret

Both must be set together — setting only one leaves GitHub unregistered
(same rule as the existing Google pair).

GitHub OAuth App setup

  1. github.com/settings/developers → OAuth Apps → New OAuth App
    (or under an org's settings, for an org-owned app).
  2. Homepage URL: your PUBLIC_BASE_URL
    (e.g. https://console.example.com).
  3. Authorization callback URL: ${PUBLIC_BASE_URL}/auth/callback/github
    — better-auth mounts the social-provider callback at /callback/:id
    under the /auth basePath.
  4. Copy the generated Client ID / Client Secret into GITHUB_CLIENT_ID /
    GITHUB_CLIENT_SECRET (.env for Docker self-host, or wrangler secret put for the Cloudflare deploy).
  5. Restart the server to pick up the new env.

Full walkthrough in docs/self-host.md (new "GitHub OAuth login (optional)"
section).

Acceptance

  • GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET set → better-auth
    registers github as a social provider; /auth-info advertises it;
    Console shows "Continue with GitHub".
  • Either/both unset → no github entry, no /auth-info change, no
    button, no crash — behavior identical to before this PR.
  • Same behavior on both Cloudflare (apps/main) and Node self-host
    (apps/main-node).
  • Unit test (packages/auth-config/src/index.test.ts) asserts
    registration/non-registration for both the paired-set and
    only-one-set cases, plus independence from the Google provider.
  • pnpm typecheck passes (root + node pass + apps/console).

Test plan

pnpm vitest run packages/auth-config/src/index.test.ts
# → Test Files  1 passed (1)  /  Tests  4 passed (4)

pnpm typecheck
# → passes (root tsc, typecheck:node, apps/console)

Per instructions, the full pnpm build / full pnpm test suite was not
run in this change — only the new test's own file and pnpm typecheck.

🤖 Generated with Claude Code

Add GitHub as a better-auth social provider, gated behind
GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET (mirrors the existing Google OAuth
wiring) on both the Cloudflare (apps/main) and Node self-host
(apps/main-node) runtimes. The Console Login page shows a
"Continue with GitHub" button when /auth-info advertises the provider.

Co-authored-by: duyet <me@duyet.net>
Co-authored-by: duyetbot <bot@duyet.net>

@sourcery-ai sourcery-ai 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.

Sorry @duyetbot, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@duyetbot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 52 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e5a6b462-2c0d-4e7b-8596-5632bbecd43d

📥 Commits

Reviewing files that changed from the base of the PR and between 98d3018 and aa7d643.

📒 Files selected for processing (10)
  • .env.example
  • apps/console/src/pages/Login.tsx
  • apps/main-node/src/index.ts
  • apps/main/src/auth-config.ts
  • apps/main/src/index.ts
  • docs/deployment.md
  • docs/self-host.md
  • packages/auth-config/src/index.test.ts
  • packages/auth-config/src/index.ts
  • packages/shared/src/env.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/github-oauth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@duyetbot
duyetbot merged commit 53d56cc into main Jul 3, 2026
6 checks passed
@duyetbot
duyetbot deleted the feat/github-oauth branch July 3, 2026 16:21

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces optional GitHub OAuth login support across the console, main-node, and main apps, updating the configuration, environment variables, documentation, and login UI to support GitHub authentication alongside Google. The review feedback suggests improving the user experience by adding error handling and loading state management to the social sign-in actions, as well as disabling the login buttons during active requests to prevent duplicate submissions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +326 to +331
const handleGithub = async () => {
await authClient.signIn.social({
provider: "github",
callbackURL: nextUrl,
});
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Social sign-in actions like authClient.signIn.social can fail before redirecting (e.g., due to network issues or misconfiguration). Adding error handling and managing the loading state will prevent silent failures and improve the user experience. You should also apply this same pattern to handleGoogle.

Suggested change
const handleGithub = async () => {
await authClient.signIn.social({
provider: "github",
callbackURL: nextUrl,
});
};
const handleGithub = async () => {
setError("");
setLoading(true);
try {
const { error } = await authClient.signIn.social({
provider: "github",
callbackURL: nextUrl,
});
if (error) {
setError(error.message || "Failed to sign in with GitHub");
}
} catch (err: any) {
setError(err?.message || "An unexpected error occurred");
} finally {
setLoading(false);
}
};

Comment on lines +409 to +412
<button
onClick={handleGithub}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 border border-border rounded-md text-sm text-fg hover:bg-bg-surface transition-colors duration-[var(--dur-quick)] ease-[var(--ease-soft)]"
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Disable the button and reduce its opacity when loading is true to prevent multiple clicks or form submissions while the redirect is in progress.

                <button
                  onClick={handleGithub}
                  disabled={loading}
                  className="w-full flex items-center justify-center gap-2 px-4 py-2.5 border border-border rounded-md text-sm text-fg hover:bg-bg-surface transition-colors duration-[var(--dur-quick)] ease-[var(--ease-soft)] disabled:opacity-50"
                >

duyetbot added a commit that referenced this pull request Jul 3, 2026
Reconcile with #27 (website), #28 (GitHub OAuth), #29 (trusted-proxy auth).
Kept the incoming feature code and applied the @duyet/oma-* rename to all
newly-added old-scope references (trusted-proxy.ts + tests, apps/main-node,
packages/auth, packages/auth-config, docs/self-host.md, deploy-website.yml,
package.json filters). Collapsed apps/web wrangler routes to the single
oma.duyet.net custom domain.

Co-authored-by: duyet <me@duyet.net>
Co-authored-by: duyetbot <bot@duyet.net>
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.

2 participants