Something went wrong. Try again.
mirror your GitHub repos to tangled.org automatically
Something went wrong. Try again.
4.0 kB · 109 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110import { App } from '@octokit/app'
let cachedApp: App | undefined
function useApp(): App { if (cachedApp) return cachedApp const appId = process.env.NUXT_GITHUB_APP_ID const privateKey = process.env.NUXT_GITHUB_APP_PRIVATE_KEY if (!appId || !privateKey) { throw new Error('NUXT_GITHUB_APP_ID and NUXT_GITHUB_APP_PRIVATE_KEY must be set') } // The OAuth client id/secret are optional at construction time so the // webhook + sync paths (server-to-server only) keep working without them; // requireOAuthApp() throws explicitly when the /connect flow needs them. const clientId = process.env.NUXT_GITHUB_APP_CLIENT_ID const clientSecret = process.env.NUXT_GITHUB_APP_CLIENT_SECRET cachedApp = new App({ appId, ...(clientId && clientSecret ? { oauth: { clientId, clientSecret } } : {}), // Vercel env vars escape newlines; restore them so PEM parsing works. privateKey: privateKey.replaceAll('\\n', '\n'), }) return cachedApp}
export type InstallationOctokit = Awaited<ReturnType<App['getInstallationOctokit']>>export type UserOctokit = Awaited<ReturnType<App['oauth']['getUserOctokit']>>
/** Get an Octokit pre-authed for a specific GitHub App installation. */export async function installationOctokit(installationId: number): Promise<InstallationOctokit> { const app = useApp() return app.getInstallationOctokit(installationId)}
/** Mint a short-lived installation access token for authenticating git fetches. */export async function installationToken(octokit: InstallationOctokit): Promise<string> { const { token } = (await octokit.auth({ type: 'installation' })) as { token: string } return token}
function requireOAuthApp(): App { if (!process.env.NUXT_GITHUB_APP_CLIENT_ID || !process.env.NUXT_GITHUB_APP_CLIENT_SECRET) { throw createError({ statusCode: 500, statusMessage: 'GitHub user-OAuth is not configured (set NUXT_GITHUB_APP_CLIENT_ID and NUXT_GITHUB_APP_CLIENT_SECRET)', }) } return useApp()}
/** * Build the GitHub user-to-server OAuth authorize URL. `state` is round-tripped * back to the callback for CSRF protection; the callback verifies it against a * sealed cookie. */export function githubOAuthUrl(opts: { state: string, redirectUri: string }): string { const app = requireOAuthApp() const { url } = app.oauth.getWebFlowAuthorizationUrl({ state: opts.state, redirectUrl: opts.redirectUri, allowSignup: false, }) return url}
/** * Exchange an OAuth `code` for a user access token and return an Octokit * authenticated as that user. */export async function userOctokitFromCode(code: string): Promise<UserOctokit> { const app = requireOAuthApp() return app.oauth.getUserOctokit({ code })}
/** * True if the authenticated user administers `installationId`. The * user-to-server `GET /user/installations` endpoint only returns installations * the user can administer, so membership in the list is the ownership proof. */export async function userAdministersInstallation( userOctokit: UserOctokit, installationId: number,): Promise<boolean> { // A user with >100 app installations is implausible here, so one page is // enough; `/user/installations` only lists installs the user administers. const { data } = await userOctokit.request('GET /user/installations', { per_page: 100 }) return data.installations.some(install => install.id === installationId)}
/** Resolve an installation's account login (for display on the connect page). */export async function installationAccountLogin(installationId: number): Promise<string | null> { const app = useApp() try { const { data } = await app.octokit.request('GET /app/installations/{installation_id}', { installation_id: installationId, }) const account = data.account if (account && 'login' in account) return account.login return null } catch { return null }}
/** Test hook. */export function clearGitHubAppCache() { cachedApp = undefined}