Skip to content
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { createAppAuth } from "@octokit/auth-app"
import { Octokit } from "@octokit/rest"
import type { Transaction } from "neo4j-driver"

import { EventBusAdapter } from "@/shared/adapters/ioredis/EventBusAdapter"
import { makeSettingsReaderAdapter } from "@/shared/adapters/neo4j/repositories/SettingsReaderAdapter"
import { StorageAdapter } from "@/shared/adapters/neo4j/StorageAdapter"
import { setAccessToken } from "@/shared/auth"
import { getPrivateKeyFromFile } from "@/shared/services/fs"
import { autoResolveIssue as autoResolveIssueWorkflow } from "@/shared/usecases/workflows/autoResolveIssue"

Expand Down Expand Up @@ -48,9 +46,9 @@ export async function autoResolveIssue(
{
repoFullName,
issueNumber,
branch,
githubLogin,
githubInstallationId,
branch,
}: AutoResolveJobData
) {
await publishJobStatus(
Expand All @@ -73,7 +71,7 @@ export async function autoResolveIssue(
const privateKey = await getPrivateKeyFromFile(GITHUB_APP_PRIVATE_KEY_PATH)
// GitHub API via App Installation

// Temporary: set the access token for the workflow
// Installation-scoped Octokit (if needed locally)
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
Expand All @@ -85,18 +83,6 @@ export async function autoResolveIssue(

// Setup adapters for event bus
const eventBusAdapter = new EventBusAdapter(REDIS_URL)
const auth = await octokit.auth({ type: "installation" })
if (
!auth ||
typeof auth !== "object" ||
!("token" in auth) ||
typeof auth.token !== "string"
) {
throw new Error("Failed to get installation token")
}

// TODO: We gotta get rid of this.
setAccessToken(auth.token)

const storage = new StorageAdapter(neo4jDs)

Expand Down
35 changes: 0 additions & 35 deletions shared/src/auth.ts

This file was deleted.

76 changes: 7 additions & 69 deletions shared/src/lib/github/graphql/queries/listUserRepositories.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,13 @@
"use server"

import { z } from "zod"

import { getUserOctokit } from "@/shared/lib/github"
import { withTiming } from "@/shared/utils/telemetry"

/**
* GraphQL document for listing the current viewer's repositories ordered by last update.
* Keep the Zod schemas below in sync with this selection set whenever you edit it.
* This function is not available in the shared package because it requires
* a user session token (NextAuth). Use the app-layer implementation instead:
* `lib/github/graphql/queries/listUserRepositories.ts`.
*/
const LIST_USER_REPOSITORIES_QUERY = `
query ListUserRepositories {
viewer {
repositories(first: 50, orderBy: { field: UPDATED_AT, direction: DESC }) {
nodes {
name
nameWithOwner
description
updatedAt
}
}
}
}
` as const

const RepoSelectorItemSchema = z.object({
name: z.string(),
nameWithOwner: z.string(),
description: z.string().nullable(),
updatedAt: z.string(),
})
type RepoSelectorItem = z.infer<typeof RepoSelectorItemSchema>

const ResponseSchema = z.object({
viewer: z.object({
repositories: z.object({
nodes: z.array(RepoSelectorItemSchema),
}),
}),
})

type ListUserRepositoriesResponse = z.infer<typeof ResponseSchema>

/**
*
* Lists repositories that are available to the user AND the Github App
* We use `getUserOctokit` to retreive the list of repositories that are visible
* to both the user AND the Github App.
* This is different from `listUserAppRepositories` which only lists repositories
* that have the Github App installed.
* Therefore, public repositories will be included in this list, even if they
* don't have the Github App installed. Since the Github App can also "see" public repositories.
*/
export async function listUserRepositories(): Promise<RepoSelectorItem[]> {
const octokit = await getUserOctokit()
const graphqlWithAuth = octokit.graphql

if (!graphqlWithAuth) {
throw new Error("Could not initialize GraphQL client")
}

const data = await withTiming("GitHub GraphQL: listUserRepositories", () =>
graphqlWithAuth<ListUserRepositoriesResponse>(LIST_USER_REPOSITORIES_QUERY)
export async function listUserRepositories(): Promise<never> {
throw new Error(
"shared: listUserRepositories is not available. Use the app-layer version that has access to user session."
)

const parsed = ResponseSchema.parse(data)

return parsed.viewer.repositories.nodes.map((node) => ({
nameWithOwner: node.nameWithOwner,
name: node.name,
description: node.description,
updatedAt: node.updatedAt,
}))
}

67 changes: 10 additions & 57 deletions shared/src/lib/github/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"use server"

import { createAppAuth } from "@octokit/auth-app"
import { createOAuthUserAuth } from "@octokit/auth-oauth-user"
import { graphql } from "@octokit/graphql"
import { Octokit } from "@octokit/rest"
import * as fs from "fs/promises"
import { App } from "octokit"

import { getAccessTokenOrThrow } from "@/shared/auth"
import type { ExtendedOctokit } from "@/shared/lib/types/github"

export async function getPrivateKeyFromFile(): Promise<string> {
Expand All @@ -19,34 +17,21 @@ export async function getPrivateKeyFromFile(): Promise<string> {
}

/**
* Creates an authenticated Octokit client using one of two authentication methods:
* 1. User Authentication: Tries to use the user's session token first
* 2. GitHub App Authentication: Falls back to using GitHub App credentials (private key + app ID)
* if user authentication fails
*
* Returns either an authenticated Octokit instance or null if both auth methods fail
*
* @deprecated Use getUserOctokit or getInstallationOctokit instead
* @deprecated Not available in shared package. Use repo-scoped installation clients instead.
*/
export default async function getOctokit(): Promise<ExtendedOctokit | null> {
const token = getAccessTokenOrThrow()

const userOctokit = new Octokit({ auth: token })

return { ...userOctokit, authType: "user" }
throw new Error(
"shared/lib/github:getOctokit is deprecated. Use installation-scoped clients (getInstallationOctokit) at the call site."
)
}

/**
* Creates an authenticated GraphQL client using Github App Authentication
* @deprecated Not available in shared package. Use installation-scoped clients instead.
*/
export async function getGraphQLClient(): Promise<typeof graphql | null> {
const octokit = await getOctokit()

if (!octokit) {
return null
}

return octokit.graphql
throw new Error(
"shared/lib/github:getGraphQLClient is deprecated. Use installationOctokit.graphql for a specific repo."
)
}

// TODO: Get rid of
Expand Down Expand Up @@ -74,41 +59,8 @@ export async function getTestInstallationOctokit(
}

/**
* Creates an authenticated Octokit client using the OAuth user authentication strategy.
* This function uses the existing session tokens from NextAuth to authenticate with GitHub.
*
* This is an alternative to getUserOctokit() that uses the @octokit/auth-oauth-user strategy
* instead of directly passing the access token to the Octokit constructor.
*
* @returns An authenticated Octokit instance or throws an error if authentication fails
* Creates an authenticated Octokit client for a GitHub App installation.
*/
export async function getUserOctokit(): Promise<Octokit> {
const token = getAccessTokenOrThrow()

// `clientId` and `clientSecret` are already determined by
// auth.js library when authenticating user in `auth.js`.
// No need to add them here, as they're inferred in the `access_token`
const userOctokit = new Octokit({
authStrategy: createOAuthUserAuth,
auth: {
clientType: "github-app",
token,
},
})

return userOctokit
}

export async function getUserInstallations() {
const octokit = await getUserOctokit()

const { data: installations } = await octokit.request(
"GET /user/installations"
)

return installations.installations
}

export async function getInstallationOctokit(
installationId: number
): Promise<Octokit> {
Expand Down Expand Up @@ -136,3 +88,4 @@ export async function getAppOctokit(): Promise<App> {
})
return app
}

Loading