@opengeni/github 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -1
- package/dist/index.js +49 -17
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/index.ts +161 -54
package/dist/index.d.ts
CHANGED
|
@@ -54,10 +54,18 @@ declare function createGitHubAppInstallationToken(settings: Settings, input: {
|
|
|
54
54
|
installationId: number;
|
|
55
55
|
repositoryIds?: number[];
|
|
56
56
|
}): Promise<string>;
|
|
57
|
+
type GitHubAppInstallationToken = {
|
|
58
|
+
token: string;
|
|
59
|
+
expiresAt: string | null;
|
|
60
|
+
};
|
|
61
|
+
declare function createGitHubAppInstallationTokenWithExpiry(settings: Settings, input: {
|
|
62
|
+
installationId: number;
|
|
63
|
+
repositoryIds?: number[];
|
|
64
|
+
}): Promise<GitHubAppInstallationToken>;
|
|
57
65
|
declare function githubAppBotIdentity(settings: Settings): {
|
|
58
66
|
name: string;
|
|
59
67
|
email: string;
|
|
60
68
|
} | null;
|
|
61
69
|
declare function normalizeGitHubAppPrivateKey(value: string): string;
|
|
62
70
|
|
|
63
|
-
export { GitHubAppApiError, GitHubAppConfigurationError, type GitHubAppInstallationSummary, type GitHubSignedStatePayload, buildGitHubAppManifest, convertGitHubAppManifest, createGitHubAppInstallationToken, createSignedState, envLinesFromGitHubManifestConversion, getGitHubAppInstallationSummary, githubAppBotIdentity, githubAppMissingSettings, githubOAuthAuthorizeUrl, listGitHubAppInstallationSummaries, listGitHubAppRepositories, normalizeGitHubAppPrivateKey, organizationAppManifestUrl, personalAppManifestUrl, readSignedState, stateMaxAgeSeconds, verifyGitHubInstallationAccessForUser, verifySignedState };
|
|
71
|
+
export { GitHubAppApiError, GitHubAppConfigurationError, type GitHubAppInstallationSummary, type GitHubAppInstallationToken, type GitHubSignedStatePayload, buildGitHubAppManifest, convertGitHubAppManifest, createGitHubAppInstallationToken, createGitHubAppInstallationTokenWithExpiry, createSignedState, envLinesFromGitHubManifestConversion, getGitHubAppInstallationSummary, githubAppBotIdentity, githubAppMissingSettings, githubOAuthAuthorizeUrl, listGitHubAppInstallationSummaries, listGitHubAppRepositories, normalizeGitHubAppPrivateKey, organizationAppManifestUrl, personalAppManifestUrl, readSignedState, stateMaxAgeSeconds, verifyGitHubInstallationAccessForUser, verifySignedState };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHmac, createPrivateKey, randomBytes, timingSafeEqual } from "cryp
|
|
|
3
3
|
import { SignJWT, importPKCS8 } from "jose";
|
|
4
4
|
var githubApiBase = "https://api.github.com";
|
|
5
5
|
var githubApiVersion = "2022-11-28";
|
|
6
|
+
var githubTokenMintTimeoutMs = 6e4;
|
|
6
7
|
var stateMaxAgeSeconds = 60 * 60;
|
|
7
8
|
var pkcs8PrivateKeyHeader = `-----BEGIN ${"PRIVATE KEY"}-----`;
|
|
8
9
|
var rsaPrivateKeyHeader = `-----BEGIN ${"RSA PRIVATE KEY"}-----`;
|
|
@@ -11,7 +12,6 @@ var GitHubAppConfigurationError = class extends Error {
|
|
|
11
12
|
super("GitHub App is not configured");
|
|
12
13
|
this.missing = missing;
|
|
13
14
|
}
|
|
14
|
-
missing;
|
|
15
15
|
};
|
|
16
16
|
var GitHubAppApiError = class extends Error {
|
|
17
17
|
};
|
|
@@ -142,7 +142,9 @@ async function getGitHubAppInstallationSummary(settings, installationId) {
|
|
|
142
142
|
async function verifyGitHubInstallationAccessForUser(settings, input) {
|
|
143
143
|
const token = await exchangeGitHubOAuthCodeForUserToken(settings, input.code);
|
|
144
144
|
const installations = await listUserAccessibleInstallations(token);
|
|
145
|
-
const installation = installations.find(
|
|
145
|
+
const installation = installations.find(
|
|
146
|
+
(candidate) => candidate.installationId === input.installationId
|
|
147
|
+
);
|
|
146
148
|
if (!installation) {
|
|
147
149
|
throw new GitHubAppApiError("GitHub installation is not accessible to the installing user");
|
|
148
150
|
}
|
|
@@ -173,12 +175,17 @@ async function listGitHubAppRepositories(settings, input = {}) {
|
|
|
173
175
|
}
|
|
174
176
|
const account = typeof installation.account === "object" && installation.account ? installation.account : {};
|
|
175
177
|
const token = await createInstallationToken(jwt, { installationId });
|
|
176
|
-
repositories.push(
|
|
178
|
+
repositories.push(
|
|
179
|
+
...await listInstallationRepositories(token.token, installationId, account)
|
|
180
|
+
);
|
|
177
181
|
}
|
|
178
182
|
repositories.sort((left, right) => left.fullName.localeCompare(right.fullName));
|
|
179
183
|
return repositories;
|
|
180
184
|
}
|
|
181
185
|
async function createGitHubAppInstallationToken(settings, input) {
|
|
186
|
+
return (await createGitHubAppInstallationTokenWithExpiry(settings, input)).token;
|
|
187
|
+
}
|
|
188
|
+
async function createGitHubAppInstallationTokenWithExpiry(settings, input) {
|
|
182
189
|
const missing = githubAppMissingSettings(settings);
|
|
183
190
|
if (missing.length > 0) {
|
|
184
191
|
throw new GitHubAppConfigurationError(missing);
|
|
@@ -211,11 +218,18 @@ async function createGitHubAppJwt(settings) {
|
|
|
211
218
|
async function listInstallations(token) {
|
|
212
219
|
const out = [];
|
|
213
220
|
for (let page = 1; ; page += 1) {
|
|
214
|
-
const payload = await githubGet("/app/installations", token, {
|
|
221
|
+
const payload = await githubGet("/app/installations", token, {
|
|
222
|
+
per_page: "100",
|
|
223
|
+
page: String(page)
|
|
224
|
+
});
|
|
215
225
|
if (!Array.isArray(payload)) {
|
|
216
226
|
throw new GitHubAppApiError("GitHub returned an invalid installations payload");
|
|
217
227
|
}
|
|
218
|
-
out.push(
|
|
228
|
+
out.push(
|
|
229
|
+
...payload.filter(
|
|
230
|
+
(item) => Boolean(item && typeof item === "object" && !Array.isArray(item))
|
|
231
|
+
)
|
|
232
|
+
);
|
|
219
233
|
if (payload.length < 100) {
|
|
220
234
|
return out;
|
|
221
235
|
}
|
|
@@ -249,12 +263,19 @@ async function exchangeGitHubOAuthCodeForUserToken(settings, code) {
|
|
|
249
263
|
async function listUserAccessibleInstallations(token) {
|
|
250
264
|
const out = [];
|
|
251
265
|
for (let page = 1; ; page += 1) {
|
|
252
|
-
const payload = await githubGet("/user/installations", token, {
|
|
266
|
+
const payload = await githubGet("/user/installations", token, {
|
|
267
|
+
per_page: "100",
|
|
268
|
+
page: String(page)
|
|
269
|
+
});
|
|
253
270
|
const installations = payload && typeof payload === "object" && Array.isArray(payload.installations) ? payload.installations : null;
|
|
254
271
|
if (!installations) {
|
|
255
272
|
throw new GitHubAppApiError("GitHub returned an invalid user installations payload");
|
|
256
273
|
}
|
|
257
|
-
out.push(
|
|
274
|
+
out.push(
|
|
275
|
+
...installations.filter(
|
|
276
|
+
(item) => Boolean(item && typeof item === "object" && !Array.isArray(item))
|
|
277
|
+
).map(installationSummaryFromPayload)
|
|
278
|
+
);
|
|
258
279
|
if (installations.length < 100) {
|
|
259
280
|
return out;
|
|
260
281
|
}
|
|
@@ -262,14 +283,18 @@ async function listUserAccessibleInstallations(token) {
|
|
|
262
283
|
}
|
|
263
284
|
async function createInstallationToken(appJwt, input) {
|
|
264
285
|
const scoped = input.repositoryIds && input.repositoryIds.length > 0;
|
|
265
|
-
const response = await fetch(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
const response = await fetch(
|
|
287
|
+
`${githubApiBase}/app/installations/${input.installationId}/access_tokens`,
|
|
288
|
+
{
|
|
289
|
+
method: "POST",
|
|
290
|
+
headers: {
|
|
291
|
+
...githubHeaders(appJwt),
|
|
292
|
+
...scoped ? { "Content-Type": "application/json" } : {}
|
|
293
|
+
},
|
|
294
|
+
signal: AbortSignal.timeout(githubTokenMintTimeoutMs),
|
|
295
|
+
...scoped ? { body: JSON.stringify({ repository_ids: input.repositoryIds }) } : {}
|
|
296
|
+
}
|
|
297
|
+
);
|
|
273
298
|
if (!response.ok) {
|
|
274
299
|
throw new GitHubAppApiError(await githubErrorMessage(response));
|
|
275
300
|
}
|
|
@@ -277,12 +302,18 @@ async function createInstallationToken(appJwt, input) {
|
|
|
277
302
|
if (!payload || typeof payload !== "object" || typeof payload.token !== "string") {
|
|
278
303
|
throw new GitHubAppApiError("GitHub returned an invalid installation token payload");
|
|
279
304
|
}
|
|
280
|
-
return
|
|
305
|
+
return {
|
|
306
|
+
token: payload.token,
|
|
307
|
+
expiresAt: typeof payload.expires_at === "string" ? payload.expires_at : null
|
|
308
|
+
};
|
|
281
309
|
}
|
|
282
310
|
async function listInstallationRepositories(token, installationId, account) {
|
|
283
311
|
const out = [];
|
|
284
312
|
for (let page = 1; ; page += 1) {
|
|
285
|
-
const payload = await githubGet("/installation/repositories", token, {
|
|
313
|
+
const payload = await githubGet("/installation/repositories", token, {
|
|
314
|
+
per_page: "100",
|
|
315
|
+
page: String(page)
|
|
316
|
+
});
|
|
286
317
|
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !Array.isArray(payload.repositories)) {
|
|
287
318
|
throw new GitHubAppApiError("GitHub returned an invalid repositories payload");
|
|
288
319
|
}
|
|
@@ -389,6 +420,7 @@ export {
|
|
|
389
420
|
buildGitHubAppManifest,
|
|
390
421
|
convertGitHubAppManifest,
|
|
391
422
|
createGitHubAppInstallationToken,
|
|
423
|
+
createGitHubAppInstallationTokenWithExpiry,
|
|
392
424
|
createSignedState,
|
|
393
425
|
envLinesFromGitHubManifestConversion,
|
|
394
426
|
getGitHubAppInstallationSummary,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport type { GitHubRepository } from \"@opengeni/contracts\";\nimport { createHmac, createPrivateKey, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { SignJWT, importPKCS8 } from \"jose\";\n\nconst githubApiBase = \"https://api.github.com\";\nconst githubApiVersion = \"2022-11-28\";\nexport const stateMaxAgeSeconds = 60 * 60;\nconst pkcs8PrivateKeyHeader = `-----BEGIN ${\"PRIVATE KEY\"}-----`;\nconst rsaPrivateKeyHeader = `-----BEGIN ${\"RSA PRIVATE KEY\"}-----`;\n\nexport class GitHubAppConfigurationError extends Error {\n constructor(readonly missing: string[]) {\n super(\"GitHub App is not configured\");\n }\n}\n\nexport class GitHubAppApiError extends Error {}\n\nexport type GitHubAppInstallationSummary = {\n installationId: number;\n accountLogin: string | null;\n accountType: string | null;\n suspended: boolean;\n};\n\nexport type GitHubSignedStatePayload = {\n nonce: string;\n iat: number;\n accountId?: string;\n workspaceId?: string;\n [key: string]: unknown;\n};\n\nexport function githubAppMissingSettings(settings: Settings): string[] {\n const required: Record<string, string | undefined> = {\n OPENGENI_GITHUB_APP_ID: settings.githubAppId,\n OPENGENI_GITHUB_CLIENT_ID: settings.githubClientId,\n OPENGENI_GITHUB_CLIENT_SECRET: settings.githubClientSecret,\n OPENGENI_GITHUB_APP_SLUG: settings.githubAppSlug,\n OPENGENI_GITHUB_APP_PRIVATE_KEY: settings.githubAppPrivateKey,\n };\n return Object.entries(required).flatMap(([name, value]) => value && value.trim() ? [] : [name]);\n}\n\nexport function buildGitHubAppManifest(input: {\n appName: string;\n baseUrl: string;\n public: boolean;\n includeCiPermissions: boolean;\n setupUrl?: string;\n}): Record<string, unknown> {\n const base = input.baseUrl.replace(/\\/+$/, \"\");\n const permissions: Record<string, string> = {\n metadata: \"read\",\n contents: \"write\",\n pull_requests: \"write\",\n };\n if (input.includeCiPermissions) {\n permissions.actions = \"read\";\n permissions.checks = \"read\";\n permissions.statuses = \"write\";\n }\n const manifest: Record<string, unknown> = {\n name: input.appName,\n url: base,\n redirect_url: `${base}/v1/github/app-manifest/callback`,\n public: input.public,\n request_oauth_on_install: true,\n default_permissions: permissions,\n };\n if (input.setupUrl) {\n manifest.setup_url = input.setupUrl;\n manifest.setup_on_update = true;\n }\n return manifest;\n}\n\nexport function personalAppManifestUrl(state: string): string {\n return `https://github.com/settings/apps/new?state=${state}`;\n}\n\nexport function organizationAppManifestUrl(organization: string, state: string): string {\n return `https://github.com/organizations/${encodeURIComponent(organization)}/settings/apps/new?state=${state}`;\n}\n\nexport function githubOAuthAuthorizeUrl(input: {\n clientId: string;\n state: string;\n redirectUri?: string;\n}): string {\n const url = new URL(\"https://github.com/login/oauth/authorize\");\n url.searchParams.set(\"client_id\", input.clientId);\n url.searchParams.set(\"state\", input.state);\n if (input.redirectUri) {\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n }\n return url.toString();\n}\n\nexport function createSignedState(\n secret: string,\n payloadOrNow: Record<string, unknown> | number = {},\n nowArg = Math.floor(Date.now() / 1000),\n): string {\n const payloadInput = typeof payloadOrNow === \"number\" ? {} : payloadOrNow;\n const now = typeof payloadOrNow === \"number\" ? payloadOrNow : nowArg;\n const payload = {\n ...payloadInput,\n nonce: randomBytes(16).toString(\"base64url\"),\n iat: now,\n };\n const encoded = Buffer.from(JSON.stringify(payload)).toString(\"base64url\");\n return `${encoded}.${signStatePayload(encoded, secret)}`;\n}\n\nexport function readSignedState(state: string, secret: string, now = Math.floor(Date.now() / 1000)): GitHubSignedStatePayload | null {\n const [encoded, signature] = state.split(\".\", 2);\n if (!encoded || !signature) {\n return null;\n }\n const expected = signStatePayload(encoded, secret);\n if (!safeEqual(signature, expected)) {\n return null;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(Buffer.from(encoded, \"base64url\").toString(\"utf8\"));\n } catch {\n return null;\n }\n if (!payload || typeof payload !== \"object\" || typeof (payload as { iat?: unknown }).iat !== \"number\" || typeof (payload as { nonce?: unknown }).nonce !== \"string\") {\n return null;\n }\n const age = now - (payload as { iat: number }).iat;\n return age >= 0 && age <= stateMaxAgeSeconds ? payload as GitHubSignedStatePayload : null;\n}\n\nexport function verifySignedState(state: string, secret: string, now = Math.floor(Date.now() / 1000)): boolean {\n return readSignedState(state, secret, now) !== null;\n}\n\nexport function envLinesFromGitHubManifestConversion(payload: Record<string, unknown>): string[] {\n const privateKey = String(payload.pem ?? \"\").replace(/\\n/g, \"\\\\n\");\n return [\n `OPENGENI_GITHUB_APP_ID=${payload.id ?? \"\"}`,\n `OPENGENI_GITHUB_CLIENT_ID=${payload.client_id ?? \"\"}`,\n `OPENGENI_GITHUB_CLIENT_SECRET=${payload.client_secret ?? \"\"}`,\n `OPENGENI_GITHUB_APP_SLUG=${payload.slug ?? \"\"}`,\n `OPENGENI_GITHUB_WEBHOOK_SECRET=${payload.webhook_secret ?? \"\"}`,\n `OPENGENI_GITHUB_APP_PRIVATE_KEY=\"${privateKey}\"`,\n ];\n}\n\nexport async function convertGitHubAppManifest(code: string): Promise<Record<string, unknown>> {\n const response = await fetch(`${githubApiBase}/app-manifests/${code}/conversions`, {\n method: \"POST\",\n headers: githubHeaders(undefined),\n });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n const payload = await response.json();\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new GitHubAppApiError(\"GitHub returned an invalid manifest conversion payload\");\n }\n return payload as Record<string, unknown>;\n}\n\nexport async function listGitHubAppInstallationSummaries(settings: Settings): Promise<GitHubAppInstallationSummary[]> {\n const missing = githubAppMissingSettings(settings);\n if (missing.length > 0) {\n throw new GitHubAppConfigurationError(missing);\n }\n const jwt = await createGitHubAppJwt(settings);\n const installations = await listInstallations(jwt);\n return installations.map(installationSummaryFromPayload);\n}\n\nexport async function getGitHubAppInstallationSummary(settings: Settings, installationId: number): Promise<GitHubAppInstallationSummary | null> {\n const installations = await listGitHubAppInstallationSummaries(settings);\n return installations.find((installation) => installation.installationId === installationId) ?? null;\n}\n\nexport async function verifyGitHubInstallationAccessForUser(settings: Settings, input: {\n code: string;\n installationId: number;\n}): Promise<GitHubAppInstallationSummary> {\n const token = await exchangeGitHubOAuthCodeForUserToken(settings, input.code);\n const installations = await listUserAccessibleInstallations(token);\n const installation = installations.find((candidate) => candidate.installationId === input.installationId);\n if (!installation) {\n throw new GitHubAppApiError(\"GitHub installation is not accessible to the installing user\");\n }\n return installation;\n}\n\nexport async function listGitHubAppRepositories(settings: Settings, input: {\n installationIds?: number[];\n} = {}): Promise<GitHubRepository[]> {\n const missing = githubAppMissingSettings(settings);\n if (missing.length > 0) {\n throw new GitHubAppConfigurationError(missing);\n }\n const allowedInstallations = input.installationIds ? new Set(input.installationIds) : null;\n if (allowedInstallations && allowedInstallations.size === 0) {\n return [];\n }\n const jwt = await createGitHubAppJwt(settings);\n const installations = await listInstallations(jwt);\n const repositories: GitHubRepository[] = [];\n for (const installation of installations) {\n if (installation.suspended_at) {\n continue;\n }\n const installationId = asInt(installation.id);\n if (installationId === null) {\n continue;\n }\n if (allowedInstallations && !allowedInstallations.has(installationId)) {\n continue;\n }\n const account = typeof installation.account === \"object\" && installation.account ? installation.account as Record<string, unknown> : {};\n const token = await createInstallationToken(jwt, { installationId });\n repositories.push(...await listInstallationRepositories(token, installationId, account));\n }\n repositories.sort((left, right) => left.fullName.localeCompare(right.fullName));\n return repositories;\n}\n\nexport async function createGitHubAppInstallationToken(settings: Settings, input: {\n installationId: number;\n repositoryIds?: number[];\n}): Promise<string> {\n const missing = githubAppMissingSettings(settings);\n if (missing.length > 0) {\n throw new GitHubAppConfigurationError(missing);\n }\n const jwt = await createGitHubAppJwt(settings);\n return await createInstallationToken(jwt, input);\n}\n\nexport function githubAppBotIdentity(settings: Settings): { name: string; email: string } | null {\n const appId = settings.githubAppId?.trim();\n const slug = settings.githubAppSlug?.trim();\n if (!appId || !slug) {\n return null;\n }\n const login = `${slug}[bot]`;\n return {\n name: login,\n email: `${appId}+${login}@users.noreply.github.com`,\n };\n}\n\nasync function createGitHubAppJwt(settings: Settings): Promise<string> {\n const privateKey = normalizeGitHubAppPrivateKey(settings.githubAppPrivateKey ?? \"\");\n const appId = settings.githubAppId?.trim();\n if (!appId || !privateKey) {\n throw new GitHubAppConfigurationError(githubAppMissingSettings(settings));\n }\n const key = await importPKCS8(privateKey, \"RS256\");\n const now = Math.floor(Date.now() / 1000);\n return await new SignJWT({})\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt(now - 60)\n .setExpirationTime(now + 9 * 60)\n .setIssuer(appId)\n .sign(key);\n}\n\nasync function listInstallations(token: string): Promise<Array<Record<string, unknown>>> {\n const out: Array<Record<string, unknown>> = [];\n for (let page = 1; ; page += 1) {\n const payload = await githubGet(\"/app/installations\", token, { per_page: \"100\", page: String(page) });\n if (!Array.isArray(payload)) {\n throw new GitHubAppApiError(\"GitHub returned an invalid installations payload\");\n }\n out.push(...payload.filter((item): item is Record<string, unknown> => Boolean(item && typeof item === \"object\" && !Array.isArray(item))));\n if (payload.length < 100) {\n return out;\n }\n }\n}\n\nasync function exchangeGitHubOAuthCodeForUserToken(settings: Settings, code: string): Promise<string> {\n if (!settings.githubClientId || !settings.githubClientSecret) {\n throw new GitHubAppConfigurationError(githubAppMissingSettings(settings));\n }\n const response = await fetch(\"https://github.com/login/oauth/access_token\", {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n client_id: settings.githubClientId,\n client_secret: settings.githubClientSecret,\n code,\n }),\n });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n const payload = await response.json();\n if (!payload || typeof payload !== \"object\" || typeof payload.access_token !== \"string\") {\n throw new GitHubAppApiError(\"GitHub returned an invalid OAuth token payload\");\n }\n return payload.access_token;\n}\n\nasync function listUserAccessibleInstallations(token: string): Promise<GitHubAppInstallationSummary[]> {\n const out: GitHubAppInstallationSummary[] = [];\n for (let page = 1; ; page += 1) {\n const payload = await githubGet(\"/user/installations\", token, { per_page: \"100\", page: String(page) });\n const installations: unknown[] | null = payload && typeof payload === \"object\" && Array.isArray(payload.installations)\n ? payload.installations as unknown[]\n : null;\n if (!installations) {\n throw new GitHubAppApiError(\"GitHub returned an invalid user installations payload\");\n }\n out.push(...installations\n .filter((item): item is Record<string, unknown> => Boolean(item && typeof item === \"object\" && !Array.isArray(item)))\n .map(installationSummaryFromPayload));\n if (installations.length < 100) {\n return out;\n }\n }\n}\n\nasync function createInstallationToken(appJwt: string, input: {\n installationId: number;\n repositoryIds?: number[];\n}): Promise<string> {\n const scoped = input.repositoryIds && input.repositoryIds.length > 0;\n const response = await fetch(`${githubApiBase}/app/installations/${input.installationId}/access_tokens`, {\n method: \"POST\",\n headers: {\n ...githubHeaders(appJwt),\n ...(scoped ? { \"Content-Type\": \"application/json\" } : {}),\n },\n ...(scoped ? { body: JSON.stringify({ repository_ids: input.repositoryIds }) } : {}),\n });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n const payload = await response.json();\n if (!payload || typeof payload !== \"object\" || typeof payload.token !== \"string\") {\n throw new GitHubAppApiError(\"GitHub returned an invalid installation token payload\");\n }\n return payload.token;\n}\n\nasync function listInstallationRepositories(token: string, installationId: number, account: Record<string, unknown>): Promise<GitHubRepository[]> {\n const out: GitHubRepository[] = [];\n for (let page = 1; ; page += 1) {\n const payload = await githubGet(\"/installation/repositories\", token, { per_page: \"100\", page: String(page) });\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload) || !Array.isArray(payload.repositories)) {\n throw new GitHubAppApiError(\"GitHub returned an invalid repositories payload\");\n }\n for (const repo of payload.repositories) {\n if (repo && typeof repo === \"object\" && !Array.isArray(repo)) {\n out.push(repositoryFromPayload(repo as Record<string, unknown>, installationId, account));\n }\n }\n if (payload.repositories.length < 100) {\n return out;\n }\n }\n}\n\nfunction installationSummaryFromPayload(payload: Record<string, unknown>): GitHubAppInstallationSummary {\n const installationId = asInt(payload.id);\n if (installationId === null) {\n throw new GitHubAppApiError(\"GitHub returned an installation without id\");\n }\n const account = typeof payload.account === \"object\" && payload.account ? payload.account as Record<string, unknown> : {};\n return {\n installationId,\n accountLogin: typeof account.login === \"string\" ? account.login : null,\n accountType: typeof account.type === \"string\" ? account.type : null,\n suspended: Boolean(payload.suspended_at),\n };\n}\n\nasync function githubGet(path: string, token: string, params: Record<string, string>): Promise<any> {\n const url = new URL(`${githubApiBase}${path}`);\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n const response = await fetch(url, { headers: githubHeaders(token) });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n return await response.json();\n}\n\nfunction repositoryFromPayload(payload: Record<string, unknown>, installationId: number, account: Record<string, unknown>): GitHubRepository {\n const id = asInt(payload.id);\n const fullName = String(payload.full_name ?? \"\");\n if (id === null || !fullName) {\n throw new GitHubAppApiError(\"GitHub returned a repository without id/full_name\");\n }\n return {\n id,\n installationId,\n fullName,\n name: String(payload.name ?? fullName.split(\"/\").at(-1) ?? fullName),\n private: Boolean(payload.private),\n htmlUrl: String(payload.html_url ?? `https://github.com/${fullName}`),\n cloneUrl: String(payload.clone_url ?? `https://github.com/${fullName}.git`),\n defaultBranch: String(payload.default_branch ?? \"main\"),\n accountLogin: String(account.login ?? fullName.split(\"/\", 1)[0]),\n accountType: typeof account.type === \"string\" ? account.type : null,\n };\n}\n\nfunction githubHeaders(token?: string): HeadersInit {\n return {\n Accept: \"application/vnd.github+json\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n \"X-GitHub-Api-Version\": githubApiVersion,\n };\n}\n\nasync function githubErrorMessage(response: Response): Promise<string> {\n try {\n const payload = await response.json();\n if (payload && typeof payload === \"object\" && \"message\" in payload) {\n return `GitHub API ${response.status}: ${String(payload.message)}`;\n }\n } catch {\n // fall through\n }\n return `GitHub API ${response.status}: ${await response.text()}`;\n}\n\nexport function normalizeGitHubAppPrivateKey(value: string): string {\n const privateKey = value.trim().replace(/\\\\n/g, \"\\n\");\n if (!privateKey || privateKey.startsWith(pkcs8PrivateKeyHeader)) {\n return privateKey;\n }\n if (privateKey.startsWith(rsaPrivateKeyHeader)) {\n return createPrivateKey(privateKey).export({ type: \"pkcs8\", format: \"pem\" }).toString();\n }\n return privateKey;\n}\n\nfunction signStatePayload(encoded: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(encoded).digest(\"base64url\");\n}\n\nfunction safeEqual(left: string, right: string): boolean {\n const a = Buffer.from(left);\n const b = Buffer.from(right);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nfunction asInt(value: unknown): number | null {\n if (typeof value === \"number\" && Number.isInteger(value)) {\n return value;\n }\n if (typeof value === \"string\" && /^\\d+$/.test(value)) {\n return Number(value);\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport type { GitHubRepository } from \"@opengeni/contracts\";\nimport { createHmac, createPrivateKey, randomBytes, timingSafeEqual } from \"node:crypto\";\nimport { SignJWT, importPKCS8 } from \"jose\";\n\nconst githubApiBase = \"https://api.github.com\";\nconst githubApiVersion = \"2022-11-28\";\nconst githubTokenMintTimeoutMs = 60_000;\nexport const stateMaxAgeSeconds = 60 * 60;\nconst pkcs8PrivateKeyHeader = `-----BEGIN ${\"PRIVATE KEY\"}-----`;\nconst rsaPrivateKeyHeader = `-----BEGIN ${\"RSA PRIVATE KEY\"}-----`;\n\nexport class GitHubAppConfigurationError extends Error {\n constructor(readonly missing: string[]) {\n super(\"GitHub App is not configured\");\n }\n}\n\nexport class GitHubAppApiError extends Error {}\n\nexport type GitHubAppInstallationSummary = {\n installationId: number;\n accountLogin: string | null;\n accountType: string | null;\n suspended: boolean;\n};\n\nexport type GitHubSignedStatePayload = {\n nonce: string;\n iat: number;\n accountId?: string;\n workspaceId?: string;\n [key: string]: unknown;\n};\n\nexport function githubAppMissingSettings(settings: Settings): string[] {\n const required: Record<string, string | undefined> = {\n OPENGENI_GITHUB_APP_ID: settings.githubAppId,\n OPENGENI_GITHUB_CLIENT_ID: settings.githubClientId,\n OPENGENI_GITHUB_CLIENT_SECRET: settings.githubClientSecret,\n OPENGENI_GITHUB_APP_SLUG: settings.githubAppSlug,\n OPENGENI_GITHUB_APP_PRIVATE_KEY: settings.githubAppPrivateKey,\n };\n return Object.entries(required).flatMap(([name, value]) => (value && value.trim() ? [] : [name]));\n}\n\nexport function buildGitHubAppManifest(input: {\n appName: string;\n baseUrl: string;\n public: boolean;\n includeCiPermissions: boolean;\n setupUrl?: string;\n}): Record<string, unknown> {\n const base = input.baseUrl.replace(/\\/+$/, \"\");\n const permissions: Record<string, string> = {\n metadata: \"read\",\n contents: \"write\",\n pull_requests: \"write\",\n };\n if (input.includeCiPermissions) {\n permissions.actions = \"read\";\n permissions.checks = \"read\";\n permissions.statuses = \"write\";\n }\n const manifest: Record<string, unknown> = {\n name: input.appName,\n url: base,\n redirect_url: `${base}/v1/github/app-manifest/callback`,\n public: input.public,\n request_oauth_on_install: true,\n default_permissions: permissions,\n };\n if (input.setupUrl) {\n manifest.setup_url = input.setupUrl;\n manifest.setup_on_update = true;\n }\n return manifest;\n}\n\nexport function personalAppManifestUrl(state: string): string {\n return `https://github.com/settings/apps/new?state=${state}`;\n}\n\nexport function organizationAppManifestUrl(organization: string, state: string): string {\n return `https://github.com/organizations/${encodeURIComponent(organization)}/settings/apps/new?state=${state}`;\n}\n\nexport function githubOAuthAuthorizeUrl(input: {\n clientId: string;\n state: string;\n redirectUri?: string;\n}): string {\n const url = new URL(\"https://github.com/login/oauth/authorize\");\n url.searchParams.set(\"client_id\", input.clientId);\n url.searchParams.set(\"state\", input.state);\n if (input.redirectUri) {\n url.searchParams.set(\"redirect_uri\", input.redirectUri);\n }\n return url.toString();\n}\n\nexport function createSignedState(\n secret: string,\n payloadOrNow: Record<string, unknown> | number = {},\n nowArg = Math.floor(Date.now() / 1000),\n): string {\n const payloadInput = typeof payloadOrNow === \"number\" ? {} : payloadOrNow;\n const now = typeof payloadOrNow === \"number\" ? payloadOrNow : nowArg;\n const payload = {\n ...payloadInput,\n nonce: randomBytes(16).toString(\"base64url\"),\n iat: now,\n };\n const encoded = Buffer.from(JSON.stringify(payload)).toString(\"base64url\");\n return `${encoded}.${signStatePayload(encoded, secret)}`;\n}\n\nexport function readSignedState(\n state: string,\n secret: string,\n now = Math.floor(Date.now() / 1000),\n): GitHubSignedStatePayload | null {\n const [encoded, signature] = state.split(\".\", 2);\n if (!encoded || !signature) {\n return null;\n }\n const expected = signStatePayload(encoded, secret);\n if (!safeEqual(signature, expected)) {\n return null;\n }\n let payload: unknown;\n try {\n payload = JSON.parse(Buffer.from(encoded, \"base64url\").toString(\"utf8\"));\n } catch {\n return null;\n }\n if (\n !payload ||\n typeof payload !== \"object\" ||\n typeof (payload as { iat?: unknown }).iat !== \"number\" ||\n typeof (payload as { nonce?: unknown }).nonce !== \"string\"\n ) {\n return null;\n }\n const age = now - (payload as { iat: number }).iat;\n return age >= 0 && age <= stateMaxAgeSeconds ? (payload as GitHubSignedStatePayload) : null;\n}\n\nexport function verifySignedState(\n state: string,\n secret: string,\n now = Math.floor(Date.now() / 1000),\n): boolean {\n return readSignedState(state, secret, now) !== null;\n}\n\nexport function envLinesFromGitHubManifestConversion(payload: Record<string, unknown>): string[] {\n const privateKey = String(payload.pem ?? \"\").replace(/\\n/g, \"\\\\n\");\n return [\n `OPENGENI_GITHUB_APP_ID=${payload.id ?? \"\"}`,\n `OPENGENI_GITHUB_CLIENT_ID=${payload.client_id ?? \"\"}`,\n `OPENGENI_GITHUB_CLIENT_SECRET=${payload.client_secret ?? \"\"}`,\n `OPENGENI_GITHUB_APP_SLUG=${payload.slug ?? \"\"}`,\n `OPENGENI_GITHUB_WEBHOOK_SECRET=${payload.webhook_secret ?? \"\"}`,\n `OPENGENI_GITHUB_APP_PRIVATE_KEY=\"${privateKey}\"`,\n ];\n}\n\nexport async function convertGitHubAppManifest(code: string): Promise<Record<string, unknown>> {\n const response = await fetch(`${githubApiBase}/app-manifests/${code}/conversions`, {\n method: \"POST\",\n headers: githubHeaders(undefined),\n });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n const payload = await response.json();\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new GitHubAppApiError(\"GitHub returned an invalid manifest conversion payload\");\n }\n return payload as Record<string, unknown>;\n}\n\nexport async function listGitHubAppInstallationSummaries(\n settings: Settings,\n): Promise<GitHubAppInstallationSummary[]> {\n const missing = githubAppMissingSettings(settings);\n if (missing.length > 0) {\n throw new GitHubAppConfigurationError(missing);\n }\n const jwt = await createGitHubAppJwt(settings);\n const installations = await listInstallations(jwt);\n return installations.map(installationSummaryFromPayload);\n}\n\nexport async function getGitHubAppInstallationSummary(\n settings: Settings,\n installationId: number,\n): Promise<GitHubAppInstallationSummary | null> {\n const installations = await listGitHubAppInstallationSummaries(settings);\n return (\n installations.find((installation) => installation.installationId === installationId) ?? null\n );\n}\n\nexport async function verifyGitHubInstallationAccessForUser(\n settings: Settings,\n input: {\n code: string;\n installationId: number;\n },\n): Promise<GitHubAppInstallationSummary> {\n const token = await exchangeGitHubOAuthCodeForUserToken(settings, input.code);\n const installations = await listUserAccessibleInstallations(token);\n const installation = installations.find(\n (candidate) => candidate.installationId === input.installationId,\n );\n if (!installation) {\n throw new GitHubAppApiError(\"GitHub installation is not accessible to the installing user\");\n }\n return installation;\n}\n\nexport async function listGitHubAppRepositories(\n settings: Settings,\n input: {\n installationIds?: number[];\n } = {},\n): Promise<GitHubRepository[]> {\n const missing = githubAppMissingSettings(settings);\n if (missing.length > 0) {\n throw new GitHubAppConfigurationError(missing);\n }\n const allowedInstallations = input.installationIds ? new Set(input.installationIds) : null;\n if (allowedInstallations && allowedInstallations.size === 0) {\n return [];\n }\n const jwt = await createGitHubAppJwt(settings);\n const installations = await listInstallations(jwt);\n const repositories: GitHubRepository[] = [];\n for (const installation of installations) {\n if (installation.suspended_at) {\n continue;\n }\n const installationId = asInt(installation.id);\n if (installationId === null) {\n continue;\n }\n if (allowedInstallations && !allowedInstallations.has(installationId)) {\n continue;\n }\n const account =\n typeof installation.account === \"object\" && installation.account\n ? (installation.account as Record<string, unknown>)\n : {};\n const token = await createInstallationToken(jwt, { installationId });\n repositories.push(\n ...(await listInstallationRepositories(token.token, installationId, account)),\n );\n }\n repositories.sort((left, right) => left.fullName.localeCompare(right.fullName));\n return repositories;\n}\n\nexport async function createGitHubAppInstallationToken(\n settings: Settings,\n input: {\n installationId: number;\n repositoryIds?: number[];\n },\n): Promise<string> {\n return (await createGitHubAppInstallationTokenWithExpiry(settings, input)).token;\n}\n\nexport type GitHubAppInstallationToken = {\n token: string;\n expiresAt: string | null;\n};\n\nexport async function createGitHubAppInstallationTokenWithExpiry(\n settings: Settings,\n input: {\n installationId: number;\n repositoryIds?: number[];\n },\n): Promise<GitHubAppInstallationToken> {\n const missing = githubAppMissingSettings(settings);\n if (missing.length > 0) {\n throw new GitHubAppConfigurationError(missing);\n }\n const jwt = await createGitHubAppJwt(settings);\n return await createInstallationToken(jwt, input);\n}\n\nexport function githubAppBotIdentity(settings: Settings): { name: string; email: string } | null {\n const appId = settings.githubAppId?.trim();\n const slug = settings.githubAppSlug?.trim();\n if (!appId || !slug) {\n return null;\n }\n const login = `${slug}[bot]`;\n return {\n name: login,\n email: `${appId}+${login}@users.noreply.github.com`,\n };\n}\n\nasync function createGitHubAppJwt(settings: Settings): Promise<string> {\n const privateKey = normalizeGitHubAppPrivateKey(settings.githubAppPrivateKey ?? \"\");\n const appId = settings.githubAppId?.trim();\n if (!appId || !privateKey) {\n throw new GitHubAppConfigurationError(githubAppMissingSettings(settings));\n }\n const key = await importPKCS8(privateKey, \"RS256\");\n const now = Math.floor(Date.now() / 1000);\n return await new SignJWT({})\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt(now - 60)\n .setExpirationTime(now + 9 * 60)\n .setIssuer(appId)\n .sign(key);\n}\n\nasync function listInstallations(token: string): Promise<Array<Record<string, unknown>>> {\n const out: Array<Record<string, unknown>> = [];\n for (let page = 1; ; page += 1) {\n const payload = await githubGet(\"/app/installations\", token, {\n per_page: \"100\",\n page: String(page),\n });\n if (!Array.isArray(payload)) {\n throw new GitHubAppApiError(\"GitHub returned an invalid installations payload\");\n }\n out.push(\n ...payload.filter((item): item is Record<string, unknown> =>\n Boolean(item && typeof item === \"object\" && !Array.isArray(item)),\n ),\n );\n if (payload.length < 100) {\n return out;\n }\n }\n}\n\nasync function exchangeGitHubOAuthCodeForUserToken(\n settings: Settings,\n code: string,\n): Promise<string> {\n if (!settings.githubClientId || !settings.githubClientSecret) {\n throw new GitHubAppConfigurationError(githubAppMissingSettings(settings));\n }\n const response = await fetch(\"https://github.com/login/oauth/access_token\", {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n client_id: settings.githubClientId,\n client_secret: settings.githubClientSecret,\n code,\n }),\n });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n const payload = await response.json();\n if (!payload || typeof payload !== \"object\" || typeof payload.access_token !== \"string\") {\n throw new GitHubAppApiError(\"GitHub returned an invalid OAuth token payload\");\n }\n return payload.access_token;\n}\n\nasync function listUserAccessibleInstallations(\n token: string,\n): Promise<GitHubAppInstallationSummary[]> {\n const out: GitHubAppInstallationSummary[] = [];\n for (let page = 1; ; page += 1) {\n const payload = await githubGet(\"/user/installations\", token, {\n per_page: \"100\",\n page: String(page),\n });\n const installations: unknown[] | null =\n payload && typeof payload === \"object\" && Array.isArray(payload.installations)\n ? (payload.installations as unknown[])\n : null;\n if (!installations) {\n throw new GitHubAppApiError(\"GitHub returned an invalid user installations payload\");\n }\n out.push(\n ...installations\n .filter((item): item is Record<string, unknown> =>\n Boolean(item && typeof item === \"object\" && !Array.isArray(item)),\n )\n .map(installationSummaryFromPayload),\n );\n if (installations.length < 100) {\n return out;\n }\n }\n}\n\nasync function createInstallationToken(\n appJwt: string,\n input: {\n installationId: number;\n repositoryIds?: number[];\n },\n): Promise<GitHubAppInstallationToken> {\n const scoped = input.repositoryIds && input.repositoryIds.length > 0;\n const response = await fetch(\n `${githubApiBase}/app/installations/${input.installationId}/access_tokens`,\n {\n method: \"POST\",\n headers: {\n ...githubHeaders(appJwt),\n ...(scoped ? { \"Content-Type\": \"application/json\" } : {}),\n },\n signal: AbortSignal.timeout(githubTokenMintTimeoutMs),\n ...(scoped ? { body: JSON.stringify({ repository_ids: input.repositoryIds }) } : {}),\n },\n );\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n const payload = await response.json();\n if (!payload || typeof payload !== \"object\" || typeof payload.token !== \"string\") {\n throw new GitHubAppApiError(\"GitHub returned an invalid installation token payload\");\n }\n return {\n token: payload.token,\n expiresAt: typeof payload.expires_at === \"string\" ? payload.expires_at : null,\n };\n}\n\nasync function listInstallationRepositories(\n token: string,\n installationId: number,\n account: Record<string, unknown>,\n): Promise<GitHubRepository[]> {\n const out: GitHubRepository[] = [];\n for (let page = 1; ; page += 1) {\n const payload = await githubGet(\"/installation/repositories\", token, {\n per_page: \"100\",\n page: String(page),\n });\n if (\n !payload ||\n typeof payload !== \"object\" ||\n Array.isArray(payload) ||\n !Array.isArray(payload.repositories)\n ) {\n throw new GitHubAppApiError(\"GitHub returned an invalid repositories payload\");\n }\n for (const repo of payload.repositories) {\n if (repo && typeof repo === \"object\" && !Array.isArray(repo)) {\n out.push(repositoryFromPayload(repo as Record<string, unknown>, installationId, account));\n }\n }\n if (payload.repositories.length < 100) {\n return out;\n }\n }\n}\n\nfunction installationSummaryFromPayload(\n payload: Record<string, unknown>,\n): GitHubAppInstallationSummary {\n const installationId = asInt(payload.id);\n if (installationId === null) {\n throw new GitHubAppApiError(\"GitHub returned an installation without id\");\n }\n const account =\n typeof payload.account === \"object\" && payload.account\n ? (payload.account as Record<string, unknown>)\n : {};\n return {\n installationId,\n accountLogin: typeof account.login === \"string\" ? account.login : null,\n accountType: typeof account.type === \"string\" ? account.type : null,\n suspended: Boolean(payload.suspended_at),\n };\n}\n\nasync function githubGet(\n path: string,\n token: string,\n params: Record<string, string>,\n): Promise<any> {\n const url = new URL(`${githubApiBase}${path}`);\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n const response = await fetch(url, { headers: githubHeaders(token) });\n if (!response.ok) {\n throw new GitHubAppApiError(await githubErrorMessage(response));\n }\n return await response.json();\n}\n\nfunction repositoryFromPayload(\n payload: Record<string, unknown>,\n installationId: number,\n account: Record<string, unknown>,\n): GitHubRepository {\n const id = asInt(payload.id);\n const fullName = String(payload.full_name ?? \"\");\n if (id === null || !fullName) {\n throw new GitHubAppApiError(\"GitHub returned a repository without id/full_name\");\n }\n return {\n id,\n installationId,\n fullName,\n name: String(payload.name ?? fullName.split(\"/\").at(-1) ?? fullName),\n private: Boolean(payload.private),\n htmlUrl: String(payload.html_url ?? `https://github.com/${fullName}`),\n cloneUrl: String(payload.clone_url ?? `https://github.com/${fullName}.git`),\n defaultBranch: String(payload.default_branch ?? \"main\"),\n accountLogin: String(account.login ?? fullName.split(\"/\", 1)[0]),\n accountType: typeof account.type === \"string\" ? account.type : null,\n };\n}\n\nfunction githubHeaders(token?: string): HeadersInit {\n return {\n Accept: \"application/vnd.github+json\",\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n \"X-GitHub-Api-Version\": githubApiVersion,\n };\n}\n\nasync function githubErrorMessage(response: Response): Promise<string> {\n try {\n const payload = await response.json();\n if (payload && typeof payload === \"object\" && \"message\" in payload) {\n return `GitHub API ${response.status}: ${String(payload.message)}`;\n }\n } catch {\n // fall through\n }\n return `GitHub API ${response.status}: ${await response.text()}`;\n}\n\nexport function normalizeGitHubAppPrivateKey(value: string): string {\n const privateKey = value.trim().replace(/\\\\n/g, \"\\n\");\n if (!privateKey || privateKey.startsWith(pkcs8PrivateKeyHeader)) {\n return privateKey;\n }\n if (privateKey.startsWith(rsaPrivateKeyHeader)) {\n return createPrivateKey(privateKey).export({ type: \"pkcs8\", format: \"pem\" }).toString();\n }\n return privateKey;\n}\n\nfunction signStatePayload(encoded: string, secret: string): string {\n return createHmac(\"sha256\", secret).update(encoded).digest(\"base64url\");\n}\n\nfunction safeEqual(left: string, right: string): boolean {\n const a = Buffer.from(left);\n const b = Buffer.from(right);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nfunction asInt(value: unknown): number | null {\n if (typeof value === \"number\" && Number.isInteger(value)) {\n return value;\n }\n if (typeof value === \"string\" && /^\\d+$/.test(value)) {\n return Number(value);\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/github",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.2.10",
|
|
5
4
|
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
|
|
8
|
+
"directory": "packages/github"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
6
15
|
"main": "./dist/index.js",
|
|
7
16
|
"module": "./dist/index.js",
|
|
8
17
|
"types": "./dist/index.d.ts",
|
|
@@ -12,27 +21,18 @@
|
|
|
12
21
|
"import": "./dist/index.js"
|
|
13
22
|
}
|
|
14
23
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"src"
|
|
18
|
-
],
|
|
19
24
|
"publishConfig": {
|
|
20
25
|
"access": "public",
|
|
21
26
|
"provenance": true
|
|
22
27
|
},
|
|
23
28
|
"scripts": {
|
|
24
29
|
"build": "tsup",
|
|
25
|
-
"typecheck": "
|
|
30
|
+
"typecheck": "tsgo --noEmit",
|
|
26
31
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
27
32
|
},
|
|
28
33
|
"dependencies": {
|
|
29
|
-
"@opengeni/config": "^0.
|
|
30
|
-
"@opengeni/contracts": "^0.
|
|
34
|
+
"@opengeni/config": "^0.5.1",
|
|
35
|
+
"@opengeni/contracts": "^0.10.0",
|
|
31
36
|
"jose": "^6.1.3"
|
|
32
|
-
},
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
|
|
36
|
-
"directory": "packages/github"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { SignJWT, importPKCS8 } from "jose";
|
|
|
5
5
|
|
|
6
6
|
const githubApiBase = "https://api.github.com";
|
|
7
7
|
const githubApiVersion = "2022-11-28";
|
|
8
|
+
const githubTokenMintTimeoutMs = 60_000;
|
|
8
9
|
export const stateMaxAgeSeconds = 60 * 60;
|
|
9
10
|
const pkcs8PrivateKeyHeader = `-----BEGIN ${"PRIVATE KEY"}-----`;
|
|
10
11
|
const rsaPrivateKeyHeader = `-----BEGIN ${"RSA PRIVATE KEY"}-----`;
|
|
@@ -40,7 +41,7 @@ export function githubAppMissingSettings(settings: Settings): string[] {
|
|
|
40
41
|
OPENGENI_GITHUB_APP_SLUG: settings.githubAppSlug,
|
|
41
42
|
OPENGENI_GITHUB_APP_PRIVATE_KEY: settings.githubAppPrivateKey,
|
|
42
43
|
};
|
|
43
|
-
return Object.entries(required).flatMap(([name, value]) => value && value.trim() ? [] : [name]);
|
|
44
|
+
return Object.entries(required).flatMap(([name, value]) => (value && value.trim() ? [] : [name]));
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export function buildGitHubAppManifest(input: {
|
|
@@ -114,7 +115,11 @@ export function createSignedState(
|
|
|
114
115
|
return `${encoded}.${signStatePayload(encoded, secret)}`;
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
export function readSignedState(
|
|
118
|
+
export function readSignedState(
|
|
119
|
+
state: string,
|
|
120
|
+
secret: string,
|
|
121
|
+
now = Math.floor(Date.now() / 1000),
|
|
122
|
+
): GitHubSignedStatePayload | null {
|
|
118
123
|
const [encoded, signature] = state.split(".", 2);
|
|
119
124
|
if (!encoded || !signature) {
|
|
120
125
|
return null;
|
|
@@ -129,14 +134,23 @@ export function readSignedState(state: string, secret: string, now = Math.floor(
|
|
|
129
134
|
} catch {
|
|
130
135
|
return null;
|
|
131
136
|
}
|
|
132
|
-
if (
|
|
137
|
+
if (
|
|
138
|
+
!payload ||
|
|
139
|
+
typeof payload !== "object" ||
|
|
140
|
+
typeof (payload as { iat?: unknown }).iat !== "number" ||
|
|
141
|
+
typeof (payload as { nonce?: unknown }).nonce !== "string"
|
|
142
|
+
) {
|
|
133
143
|
return null;
|
|
134
144
|
}
|
|
135
145
|
const age = now - (payload as { iat: number }).iat;
|
|
136
|
-
return age >= 0 && age <= stateMaxAgeSeconds ? payload as GitHubSignedStatePayload : null;
|
|
146
|
+
return age >= 0 && age <= stateMaxAgeSeconds ? (payload as GitHubSignedStatePayload) : null;
|
|
137
147
|
}
|
|
138
148
|
|
|
139
|
-
export function verifySignedState(
|
|
149
|
+
export function verifySignedState(
|
|
150
|
+
state: string,
|
|
151
|
+
secret: string,
|
|
152
|
+
now = Math.floor(Date.now() / 1000),
|
|
153
|
+
): boolean {
|
|
140
154
|
return readSignedState(state, secret, now) !== null;
|
|
141
155
|
}
|
|
142
156
|
|
|
@@ -167,7 +181,9 @@ export async function convertGitHubAppManifest(code: string): Promise<Record<str
|
|
|
167
181
|
return payload as Record<string, unknown>;
|
|
168
182
|
}
|
|
169
183
|
|
|
170
|
-
export async function listGitHubAppInstallationSummaries(
|
|
184
|
+
export async function listGitHubAppInstallationSummaries(
|
|
185
|
+
settings: Settings,
|
|
186
|
+
): Promise<GitHubAppInstallationSummary[]> {
|
|
171
187
|
const missing = githubAppMissingSettings(settings);
|
|
172
188
|
if (missing.length > 0) {
|
|
173
189
|
throw new GitHubAppConfigurationError(missing);
|
|
@@ -177,27 +193,40 @@ export async function listGitHubAppInstallationSummaries(settings: Settings): Pr
|
|
|
177
193
|
return installations.map(installationSummaryFromPayload);
|
|
178
194
|
}
|
|
179
195
|
|
|
180
|
-
export async function getGitHubAppInstallationSummary(
|
|
196
|
+
export async function getGitHubAppInstallationSummary(
|
|
197
|
+
settings: Settings,
|
|
198
|
+
installationId: number,
|
|
199
|
+
): Promise<GitHubAppInstallationSummary | null> {
|
|
181
200
|
const installations = await listGitHubAppInstallationSummaries(settings);
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
201
|
+
return (
|
|
202
|
+
installations.find((installation) => installation.installationId === installationId) ?? null
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function verifyGitHubInstallationAccessForUser(
|
|
207
|
+
settings: Settings,
|
|
208
|
+
input: {
|
|
209
|
+
code: string;
|
|
210
|
+
installationId: number;
|
|
211
|
+
},
|
|
212
|
+
): Promise<GitHubAppInstallationSummary> {
|
|
189
213
|
const token = await exchangeGitHubOAuthCodeForUserToken(settings, input.code);
|
|
190
214
|
const installations = await listUserAccessibleInstallations(token);
|
|
191
|
-
const installation = installations.find(
|
|
215
|
+
const installation = installations.find(
|
|
216
|
+
(candidate) => candidate.installationId === input.installationId,
|
|
217
|
+
);
|
|
192
218
|
if (!installation) {
|
|
193
219
|
throw new GitHubAppApiError("GitHub installation is not accessible to the installing user");
|
|
194
220
|
}
|
|
195
221
|
return installation;
|
|
196
222
|
}
|
|
197
223
|
|
|
198
|
-
export async function listGitHubAppRepositories(
|
|
199
|
-
|
|
200
|
-
|
|
224
|
+
export async function listGitHubAppRepositories(
|
|
225
|
+
settings: Settings,
|
|
226
|
+
input: {
|
|
227
|
+
installationIds?: number[];
|
|
228
|
+
} = {},
|
|
229
|
+
): Promise<GitHubRepository[]> {
|
|
201
230
|
const missing = githubAppMissingSettings(settings);
|
|
202
231
|
if (missing.length > 0) {
|
|
203
232
|
throw new GitHubAppConfigurationError(missing);
|
|
@@ -220,18 +249,41 @@ export async function listGitHubAppRepositories(settings: Settings, input: {
|
|
|
220
249
|
if (allowedInstallations && !allowedInstallations.has(installationId)) {
|
|
221
250
|
continue;
|
|
222
251
|
}
|
|
223
|
-
const account =
|
|
252
|
+
const account =
|
|
253
|
+
typeof installation.account === "object" && installation.account
|
|
254
|
+
? (installation.account as Record<string, unknown>)
|
|
255
|
+
: {};
|
|
224
256
|
const token = await createInstallationToken(jwt, { installationId });
|
|
225
|
-
repositories.push(
|
|
257
|
+
repositories.push(
|
|
258
|
+
...(await listInstallationRepositories(token.token, installationId, account)),
|
|
259
|
+
);
|
|
226
260
|
}
|
|
227
261
|
repositories.sort((left, right) => left.fullName.localeCompare(right.fullName));
|
|
228
262
|
return repositories;
|
|
229
263
|
}
|
|
230
264
|
|
|
231
|
-
export async function createGitHubAppInstallationToken(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
265
|
+
export async function createGitHubAppInstallationToken(
|
|
266
|
+
settings: Settings,
|
|
267
|
+
input: {
|
|
268
|
+
installationId: number;
|
|
269
|
+
repositoryIds?: number[];
|
|
270
|
+
},
|
|
271
|
+
): Promise<string> {
|
|
272
|
+
return (await createGitHubAppInstallationTokenWithExpiry(settings, input)).token;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export type GitHubAppInstallationToken = {
|
|
276
|
+
token: string;
|
|
277
|
+
expiresAt: string | null;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export async function createGitHubAppInstallationTokenWithExpiry(
|
|
281
|
+
settings: Settings,
|
|
282
|
+
input: {
|
|
283
|
+
installationId: number;
|
|
284
|
+
repositoryIds?: number[];
|
|
285
|
+
},
|
|
286
|
+
): Promise<GitHubAppInstallationToken> {
|
|
235
287
|
const missing = githubAppMissingSettings(settings);
|
|
236
288
|
if (missing.length > 0) {
|
|
237
289
|
throw new GitHubAppConfigurationError(missing);
|
|
@@ -272,18 +324,28 @@ async function createGitHubAppJwt(settings: Settings): Promise<string> {
|
|
|
272
324
|
async function listInstallations(token: string): Promise<Array<Record<string, unknown>>> {
|
|
273
325
|
const out: Array<Record<string, unknown>> = [];
|
|
274
326
|
for (let page = 1; ; page += 1) {
|
|
275
|
-
const payload = await githubGet("/app/installations", token, {
|
|
327
|
+
const payload = await githubGet("/app/installations", token, {
|
|
328
|
+
per_page: "100",
|
|
329
|
+
page: String(page),
|
|
330
|
+
});
|
|
276
331
|
if (!Array.isArray(payload)) {
|
|
277
332
|
throw new GitHubAppApiError("GitHub returned an invalid installations payload");
|
|
278
333
|
}
|
|
279
|
-
out.push(
|
|
334
|
+
out.push(
|
|
335
|
+
...payload.filter((item): item is Record<string, unknown> =>
|
|
336
|
+
Boolean(item && typeof item === "object" && !Array.isArray(item)),
|
|
337
|
+
),
|
|
338
|
+
);
|
|
280
339
|
if (payload.length < 100) {
|
|
281
340
|
return out;
|
|
282
341
|
}
|
|
283
342
|
}
|
|
284
343
|
}
|
|
285
344
|
|
|
286
|
-
async function exchangeGitHubOAuthCodeForUserToken(
|
|
345
|
+
async function exchangeGitHubOAuthCodeForUserToken(
|
|
346
|
+
settings: Settings,
|
|
347
|
+
code: string,
|
|
348
|
+
): Promise<string> {
|
|
287
349
|
if (!settings.githubClientId || !settings.githubClientSecret) {
|
|
288
350
|
throw new GitHubAppConfigurationError(githubAppMissingSettings(settings));
|
|
289
351
|
}
|
|
@@ -309,38 +371,55 @@ async function exchangeGitHubOAuthCodeForUserToken(settings: Settings, code: str
|
|
|
309
371
|
return payload.access_token;
|
|
310
372
|
}
|
|
311
373
|
|
|
312
|
-
async function listUserAccessibleInstallations(
|
|
374
|
+
async function listUserAccessibleInstallations(
|
|
375
|
+
token: string,
|
|
376
|
+
): Promise<GitHubAppInstallationSummary[]> {
|
|
313
377
|
const out: GitHubAppInstallationSummary[] = [];
|
|
314
378
|
for (let page = 1; ; page += 1) {
|
|
315
|
-
const payload = await githubGet("/user/installations", token, {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
379
|
+
const payload = await githubGet("/user/installations", token, {
|
|
380
|
+
per_page: "100",
|
|
381
|
+
page: String(page),
|
|
382
|
+
});
|
|
383
|
+
const installations: unknown[] | null =
|
|
384
|
+
payload && typeof payload === "object" && Array.isArray(payload.installations)
|
|
385
|
+
? (payload.installations as unknown[])
|
|
386
|
+
: null;
|
|
319
387
|
if (!installations) {
|
|
320
388
|
throw new GitHubAppApiError("GitHub returned an invalid user installations payload");
|
|
321
389
|
}
|
|
322
|
-
out.push(
|
|
323
|
-
|
|
324
|
-
|
|
390
|
+
out.push(
|
|
391
|
+
...installations
|
|
392
|
+
.filter((item): item is Record<string, unknown> =>
|
|
393
|
+
Boolean(item && typeof item === "object" && !Array.isArray(item)),
|
|
394
|
+
)
|
|
395
|
+
.map(installationSummaryFromPayload),
|
|
396
|
+
);
|
|
325
397
|
if (installations.length < 100) {
|
|
326
398
|
return out;
|
|
327
399
|
}
|
|
328
400
|
}
|
|
329
401
|
}
|
|
330
402
|
|
|
331
|
-
async function createInstallationToken(
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
403
|
+
async function createInstallationToken(
|
|
404
|
+
appJwt: string,
|
|
405
|
+
input: {
|
|
406
|
+
installationId: number;
|
|
407
|
+
repositoryIds?: number[];
|
|
408
|
+
},
|
|
409
|
+
): Promise<GitHubAppInstallationToken> {
|
|
335
410
|
const scoped = input.repositoryIds && input.repositoryIds.length > 0;
|
|
336
|
-
const response = await fetch(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
411
|
+
const response = await fetch(
|
|
412
|
+
`${githubApiBase}/app/installations/${input.installationId}/access_tokens`,
|
|
413
|
+
{
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: {
|
|
416
|
+
...githubHeaders(appJwt),
|
|
417
|
+
...(scoped ? { "Content-Type": "application/json" } : {}),
|
|
418
|
+
},
|
|
419
|
+
signal: AbortSignal.timeout(githubTokenMintTimeoutMs),
|
|
420
|
+
...(scoped ? { body: JSON.stringify({ repository_ids: input.repositoryIds }) } : {}),
|
|
341
421
|
},
|
|
342
|
-
|
|
343
|
-
});
|
|
422
|
+
);
|
|
344
423
|
if (!response.ok) {
|
|
345
424
|
throw new GitHubAppApiError(await githubErrorMessage(response));
|
|
346
425
|
}
|
|
@@ -348,14 +427,29 @@ async function createInstallationToken(appJwt: string, input: {
|
|
|
348
427
|
if (!payload || typeof payload !== "object" || typeof payload.token !== "string") {
|
|
349
428
|
throw new GitHubAppApiError("GitHub returned an invalid installation token payload");
|
|
350
429
|
}
|
|
351
|
-
return
|
|
430
|
+
return {
|
|
431
|
+
token: payload.token,
|
|
432
|
+
expiresAt: typeof payload.expires_at === "string" ? payload.expires_at : null,
|
|
433
|
+
};
|
|
352
434
|
}
|
|
353
435
|
|
|
354
|
-
async function listInstallationRepositories(
|
|
436
|
+
async function listInstallationRepositories(
|
|
437
|
+
token: string,
|
|
438
|
+
installationId: number,
|
|
439
|
+
account: Record<string, unknown>,
|
|
440
|
+
): Promise<GitHubRepository[]> {
|
|
355
441
|
const out: GitHubRepository[] = [];
|
|
356
442
|
for (let page = 1; ; page += 1) {
|
|
357
|
-
const payload = await githubGet("/installation/repositories", token, {
|
|
358
|
-
|
|
443
|
+
const payload = await githubGet("/installation/repositories", token, {
|
|
444
|
+
per_page: "100",
|
|
445
|
+
page: String(page),
|
|
446
|
+
});
|
|
447
|
+
if (
|
|
448
|
+
!payload ||
|
|
449
|
+
typeof payload !== "object" ||
|
|
450
|
+
Array.isArray(payload) ||
|
|
451
|
+
!Array.isArray(payload.repositories)
|
|
452
|
+
) {
|
|
359
453
|
throw new GitHubAppApiError("GitHub returned an invalid repositories payload");
|
|
360
454
|
}
|
|
361
455
|
for (const repo of payload.repositories) {
|
|
@@ -369,12 +463,17 @@ async function listInstallationRepositories(token: string, installationId: numbe
|
|
|
369
463
|
}
|
|
370
464
|
}
|
|
371
465
|
|
|
372
|
-
function installationSummaryFromPayload(
|
|
466
|
+
function installationSummaryFromPayload(
|
|
467
|
+
payload: Record<string, unknown>,
|
|
468
|
+
): GitHubAppInstallationSummary {
|
|
373
469
|
const installationId = asInt(payload.id);
|
|
374
470
|
if (installationId === null) {
|
|
375
471
|
throw new GitHubAppApiError("GitHub returned an installation without id");
|
|
376
472
|
}
|
|
377
|
-
const account =
|
|
473
|
+
const account =
|
|
474
|
+
typeof payload.account === "object" && payload.account
|
|
475
|
+
? (payload.account as Record<string, unknown>)
|
|
476
|
+
: {};
|
|
378
477
|
return {
|
|
379
478
|
installationId,
|
|
380
479
|
accountLogin: typeof account.login === "string" ? account.login : null,
|
|
@@ -383,7 +482,11 @@ function installationSummaryFromPayload(payload: Record<string, unknown>): GitHu
|
|
|
383
482
|
};
|
|
384
483
|
}
|
|
385
484
|
|
|
386
|
-
async function githubGet(
|
|
485
|
+
async function githubGet(
|
|
486
|
+
path: string,
|
|
487
|
+
token: string,
|
|
488
|
+
params: Record<string, string>,
|
|
489
|
+
): Promise<any> {
|
|
387
490
|
const url = new URL(`${githubApiBase}${path}`);
|
|
388
491
|
for (const [key, value] of Object.entries(params)) {
|
|
389
492
|
url.searchParams.set(key, value);
|
|
@@ -395,7 +498,11 @@ async function githubGet(path: string, token: string, params: Record<string, str
|
|
|
395
498
|
return await response.json();
|
|
396
499
|
}
|
|
397
500
|
|
|
398
|
-
function repositoryFromPayload(
|
|
501
|
+
function repositoryFromPayload(
|
|
502
|
+
payload: Record<string, unknown>,
|
|
503
|
+
installationId: number,
|
|
504
|
+
account: Record<string, unknown>,
|
|
505
|
+
): GitHubRepository {
|
|
399
506
|
const id = asInt(payload.id);
|
|
400
507
|
const fullName = String(payload.full_name ?? "");
|
|
401
508
|
if (id === null || !fullName) {
|