Files
supabase/apps/docs/lib/octokit.auth.ts
Barry Roodt f89c362b26 fix(docs): accept a GitHub token for docs content reads (#48364)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix. Complete App configurations produce the same auth options as
before.

## What is the current behavior?

Without the docs GitHub App private key, two things fail for a
contributor:

- `pnpm run embeddings` aborts before doing any work. The lint warnings
source throws, and every source shares one `Promise.all` in
[`fetchAllSources()`](https://github.com/supabase/supabase/blob/master/apps/docs/scripts/search/sources/index.ts).
- `pnpm --filter docs build` exits 1 in prebuild, so the `npm run build`
pre-flight CONTRIBUTING.md asks for cannot run either:

```
Error: DOCS_GITHUB_APP_PRIVATE_KEY environment variable is required
    at octokit (apps/docs/lib/octokit.ts:21:13)
    at fetchAiSkills (apps/docs/scripts/federated-content/fetch-federated-content.ts:258:36)
```

Both read public content, so this is a rate-limit guard rather than
access control: App auth landed in #43015 because unauthenticated calls
(60 req/hr per IP) went flaky on shared runners.

## What is the new behavior?

`apps/docs/lib/octokit.auth.ts` adds one rung below the App: a token
from `GH_TOKEN`, then `GITHUB_TOKEN` (the precedence [`gh help
environment`](https://cli.github.com/manual/gh_help_environment)
documents), so `export GH_TOKEN=$(gh auth token)` is enough to build
locally. Still authenticated, so #43015's fix holds, and still an
authenticated Octokit client, so #44274 holds.

A partially configured App is now an error naming the missing vars,
rather than falling through to a token.

Used by the lint warnings loader and `lib/octokit.ts`. The two token
vars are declared in `apps/docs/turbo.jsonc` for
`turbo/no-undeclared-env-vars`.

## Additional context

With only `GH_TOKEN` set, `turbo run build --filter=docs --force` passes
4/4 and search-index source loading completes. `pnpm test` passes (20
files, 164 tests), and `tsc --noEmit` plus `pnpm run lint` match
`origin/master`. For a complete App config the auth options are
identical to before. Happy to post the fuller verification as a comment.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added flexible GitHub authentication for documentation services,
supporting GitHub App credentials or personal access tokens.
- GitHub App authentication is preferred when fully configured, with
token-based fallback when unavailable.
- Added support for both `GH_TOKEN` and `GITHUB_TOKEN`, with clear
precedence rules.

- **Bug Fixes**
- Improved configuration validation with clear errors for missing or
incomplete authentication settings.
- Standardized authentication across GitHub content and lint-warning
retrieval.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-13 16:26:15 +00:00

71 lines
2.8 KiB
TypeScript

import { createAppAuth } from '@octokit/auth-app'
import crypto from 'node:crypto'
type AppAuth = { appId: string; installationId: string; privateKey: string }
/**
* Octokit auth options for reading public content from GitHub.
*
* Prefers the docs GitHub App (CI and production). Falls back to a personal
* access token so contributors can run the search index build locally without
* the App's private key: `GH_TOKEN` then `GITHUB_TOKEN`, matching the
* precedence the GitHub CLI documents (`gh help environment`), so an
* already-exported token just works.
*
* Both rungs authenticate on purpose: unauthenticated calls are limited to
* 60 req/hr per IP, which is what caused the flaky CI failures fixed in #43015,
* and callers here fetch one file per request. Env is read on each call rather
* than at module scope so the choice reflects the environment at call time.
*
* A partially configured App is an error rather than a token fall-back: a
* rotated-out or misnamed secret would otherwise be masked by whatever token
* happens to be in the environment, quietly reading as the wrong identity.
*
* Deliberately free of `server-only` imports: the search index scripts use this
* too, and they run outside Next.
*/
export function githubAuthOptions():
| { authStrategy: typeof createAppAuth; auth: AppAuth }
| { auth: string } {
const appId = process.env.DOCS_GITHUB_APP_ID
const installationId = process.env.DOCS_GITHUB_APP_INSTALLATION_ID
const privateKey = process.env.DOCS_GITHUB_APP_PRIVATE_KEY
if (appId && installationId && privateKey) {
return {
authStrategy: createAppAuth,
auth: {
appId,
installationId,
// https://github.com/gr2m/universal-github-app-jwt?tab=readme-ov-file#converting-pkcs1-to-pkcs8
privateKey: crypto
.createPrivateKey(privateKey)
.export({ type: 'pkcs8', format: 'pem' })
.toString(),
},
}
}
const appVars: Array<[string, string | undefined]> = [
['DOCS_GITHUB_APP_ID', appId],
['DOCS_GITHUB_APP_INSTALLATION_ID', installationId],
['DOCS_GITHUB_APP_PRIVATE_KEY', privateKey],
]
const missing = appVars.filter(([, value]) => !value).map(([name]) => name)
const partiallyConfigured = missing.length < appVars.length
if (partiallyConfigured) {
throw new Error(
`Incomplete GitHub App configuration: ${missing.join(', ')} not set. Set all three, or unset the others to authenticate with GH_TOKEN / GITHUB_TOKEN instead.`
)
}
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN
if (token) {
return { auth: token }
}
throw new Error(
'Missing GitHub credentials. Set DOCS_GITHUB_APP_ID, DOCS_GITHUB_APP_INSTALLATION_ID, and DOCS_GITHUB_APP_PRIVATE_KEY, or set GH_TOKEN / GITHUB_TOKEN for a local run (export GH_TOKEN=$(gh auth token)).'
)
}