Skip to content

Commit 1c4fd7f

Browse files
author
Frank
committed
Api: add endpoint for getting github app token
1 parent 85805d2 commit 1c4fd7f

8 files changed

Lines changed: 119 additions & 21 deletions

File tree

bun.lock

Lines changed: 37 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/app.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export const domain = (() => {
44
return `${$app.stage}.dev.opencode.ai`
55
})()
66

7+
const GITHUB_APP_ID = new sst.Secret("GITHUB_APP_ID")
8+
const GITHUB_APP_PRIVATE_KEY = new sst.Secret("GITHUB_APP_PRIVATE_KEY")
79
const bucket = new sst.cloudflare.Bucket("Bucket")
810

911
export const api = new sst.cloudflare.Worker("Api", {
@@ -13,7 +15,7 @@ export const api = new sst.cloudflare.Worker("Api", {
1315
WEB_DOMAIN: domain,
1416
},
1517
url: true,
16-
link: [bucket],
18+
link: [bucket, GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY],
1719
transform: {
1820
worker: (args) => {
1921
args.logpush = true

packages/function/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@
88
"@cloudflare/workers-types": "4.20250522.0",
99
"typescript": "catalog:",
1010
"@types/node": "catalog:"
11+
},
12+
"dependencies": {
13+
"@octokit/auth-app": "8.0.1",
14+
"jose": "6.0.11"
1115
}
1216
}

packages/function/src/api.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { DurableObject } from "cloudflare:workers"
22
import { randomUUID } from "node:crypto"
3+
import { jwtVerify, createRemoteJWKSet } from "jose"
4+
import { createAppAuth } from "@octokit/auth-app"
5+
import { Resource } from "sst"
36

47
type Env = {
58
SYNC_SERVER: DurableObjectNamespace<SyncServer>
@@ -218,5 +221,42 @@ export default {
218221
},
219222
)
220223
}
224+
225+
if (request.method === "POST" && method === "exchange_github_app_token") {
226+
const EXPECTED_AUDIENCE = "opencode-github-action"
227+
const GITHUB_ISSUER = "https://token.actions.githubusercontent.com"
228+
const JWKS_URL = `${GITHUB_ISSUER}/.well-known/jwks`
229+
230+
// get Authorization header
231+
const authHeader = request.headers.get("Authorization")
232+
const token = authHeader?.replace(/^Bearer /, "")
233+
if (!token) return new Response("Error: authorization header is required", { status: 401 })
234+
235+
// verify token
236+
const JWKS = createRemoteJWKSet(new URL(JWKS_URL))
237+
try {
238+
await jwtVerify(token, JWKS, {
239+
issuer: GITHUB_ISSUER,
240+
audience: EXPECTED_AUDIENCE,
241+
})
242+
} catch (err) {
243+
console.error("Token verification failed:", err)
244+
return new Response(JSON.stringify({ error: "Invalid or expired token" }), {
245+
status: 403,
246+
headers: { "Content-Type": "application/json" },
247+
})
248+
}
249+
250+
// Create app token
251+
const auth = createAppAuth({
252+
appId: Resource.GITHUB_APP_ID.value,
253+
privateKey: Resource.GITHUB_APP_PRIVATE_KEY.value,
254+
})
255+
const appAuthentication = await auth({ type: "app" })
256+
257+
return new Response(JSON.stringify({ token: appAuthentication.token }), {
258+
headers: { "Content-Type": "application/json" },
259+
})
260+
}
221261
},
222262
}

packages/function/sst-env.d.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,28 @@
66
import "sst"
77
declare module "sst" {
88
export interface Resource {
9-
Web: {
10-
type: "sst.cloudflare.Astro"
11-
url: string
9+
"GITHUB_APP_ID": {
10+
"type": "sst.sst.Secret"
11+
"value": string
12+
}
13+
"GITHUB_APP_PRIVATE_KEY": {
14+
"type": "sst.sst.Secret"
15+
"value": string
16+
}
17+
"Web": {
18+
"type": "sst.cloudflare.Astro"
19+
"url": string
1220
}
1321
}
1422
}
15-
// cloudflare
16-
import * as cloudflare from "@cloudflare/workers-types"
23+
// cloudflare
24+
import * as cloudflare from "@cloudflare/workers-types";
1725
declare module "sst" {
1826
export interface Resource {
19-
Api: cloudflare.Service
20-
Bucket: cloudflare.R2Bucket
27+
"Api": cloudflare.Service
28+
"Bucket": cloudflare.R2Bucket
2129
}
2230
}
2331

2432
import "sst"
25-
export {}
33+
export {}

packages/opencode/sst-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
/// <reference path="../../sst-env.d.ts" />
77

88
import "sst"
9-
export {}
9+
export {}

packages/web/sst-env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
/// <reference path="../../sst-env.d.ts" />
77

88
import "sst"
9-
export {}
9+
export {}

sst-env.d.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,28 @@
55

66
declare module "sst" {
77
export interface Resource {
8-
Api: {
9-
type: "sst.cloudflare.Worker"
10-
url: string
8+
"Api": {
9+
"type": "sst.cloudflare.Worker"
10+
"url": string
1111
}
12-
Bucket: {
13-
type: "sst.cloudflare.Bucket"
12+
"Bucket": {
13+
"type": "sst.cloudflare.Bucket"
1414
}
15-
Web: {
16-
type: "sst.cloudflare.Astro"
17-
url: string
15+
"GITHUB_APP_ID": {
16+
"type": "sst.sst.Secret"
17+
"value": string
18+
}
19+
"GITHUB_APP_PRIVATE_KEY": {
20+
"type": "sst.sst.Secret"
21+
"value": string
22+
}
23+
"Web": {
24+
"type": "sst.cloudflare.Astro"
25+
"url": string
1826
}
1927
}
2028
}
2129
/// <reference path="sst-env.d.ts" />
2230

2331
import "sst"
24-
export {}
32+
export {}

0 commit comments

Comments
 (0)