From 941c0ef71e00ec45eabd80bee67cce40102b8325 Mon Sep 17 00:00:00 2001 From: Steve Korshakov Date: Tue, 26 Aug 2025 20:08:13 -0700 Subject: [PATCH] wip: github integration --- deploy/handy.yaml | 2 + package.json | 2 + .../migration.sql | 26 ++ prisma/schema.prisma | 39 ++- sources/app/api.ts | 189 +++++++++++-- sources/app/types.ts | 39 +++ sources/dbtypes.ts | 8 + sources/main.ts | 4 + sources/modules/auth.ts | 47 +++- sources/modules/encrypt.ts | 26 ++ sources/modules/github.ts | 19 ++ yarn.lock | 263 ++++++++++++++++++ 12 files changed, 630 insertions(+), 34 deletions(-) create mode 100644 prisma/migrations/20250827015520_add_github_entities/migration.sql create mode 100644 sources/app/types.ts create mode 100644 sources/dbtypes.ts create mode 100644 sources/modules/encrypt.ts create mode 100644 sources/modules/github.ts diff --git a/deploy/handy.yaml b/deploy/handy.yaml index 336b5b9..49b78c9 100644 --- a/deploy/handy.yaml +++ b/deploy/handy.yaml @@ -62,6 +62,8 @@ spec: key: /handy-db - extract: key: /handy-master + - extract: + key: /handy-github --- apiVersion: v1 kind: Service diff --git a/package.json b/package.json index 6db03f9..bddb4fc 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dev": "lsof -ti tcp:3005 | xargs kill -9 && tsx --env-file=.env --env-file=.env.dev ./sources/main.ts", "test": "vitest run", "migrate": "dotenv -e .env.dev -- prisma migrate dev", + "migrate:reset": "dotenv -e .env.dev -- prisma migrate reset", "generate": "prisma generate", "postinstall": "prisma generate", "db": "docker run -d -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=handy -v $(pwd)/.pgdata:/var/lib/postgresql/data -p 5432:5432 postgres", @@ -43,6 +44,7 @@ "fastify-type-provider-zod": "^4.0.2", "ioredis": "^5.6.1", "jsonwebtoken": "^9.0.2", + "octokit": "^5.0.3", "pino-pretty": "^13.0.0", "prisma": "^6.11.1", "prisma-json-types-generator": "^3.5.1", diff --git a/prisma/migrations/20250827015520_add_github_entities/migration.sql b/prisma/migrations/20250827015520_add_github_entities/migration.sql new file mode 100644 index 0000000..cc6a299 --- /dev/null +++ b/prisma/migrations/20250827015520_add_github_entities/migration.sql @@ -0,0 +1,26 @@ +-- AlterTable +ALTER TABLE "Account" ADD COLUMN "githubUserId" TEXT; + +-- CreateTable +CREATE TABLE "GithubUser" ( + "id" TEXT NOT NULL, + "profile" JSONB NOT NULL, + "token" BYTEA, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GithubUser_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GithubOrganization" ( + "id" TEXT NOT NULL, + "profile" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "GithubOrganization_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_githubUserId_fkey" FOREIGN KEY ("githubUserId") REFERENCES "GithubUser"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9759dab..b5e117e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,13 +20,16 @@ datasource db { // model Account { - id String @id @default(cuid()) - publicKey String @unique - seq Int @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - settings String? - settingsVersion Int @default(0) + id String @id @default(cuid()) + publicKey String @unique + seq Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + settings String? + settingsVersion Int @default(0) + githubUserId String? + githubUser GithubUser? @relation(fields: [githubUserId], references: [id]) + Session Session[] AccountPushToken AccountPushToken[] TerminalAuthRequest TerminalAuthRequest[] @@ -106,6 +109,28 @@ model SessionMessage { @@index([sessionId, seq]) } +// +// Github +// + +model GithubUser { + id String @id + /// [GitHubProfile] + profile Json + token Bytes? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Account Account[] +} + +model GithubOrganization { + id String @id + /// [GitHubOrg] + profile Json + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + // // Utility // diff --git a/sources/app/api.ts b/sources/app/api.ts index 253ada2..90a7a59 100644 --- a/sources/app/api.ts +++ b/sources/app/api.ts @@ -12,11 +12,11 @@ import { allocateSessionSeq, allocateUserSeq } from "@/services/seq"; import { randomKeyNaked } from "@/utils/randomKeyNaked"; import { AsyncLock } from "@/utils/lock"; import { auth } from "@/modules/auth"; -import { - EventRouter, - ClientConnection, - SessionScopedConnection, - UserScopedConnection, +import { + EventRouter, + ClientConnection, + SessionScopedConnection, + UserScopedConnection, MachineScopedConnection, RecipientFilter, buildNewSessionUpdate, @@ -29,16 +29,18 @@ import { buildUsageEphemeral, buildMachineStatusEphemeral } from "@/modules/eventRouter"; -import { - incrementWebSocketConnection, - decrementWebSocketConnection, - sessionAliveEventsCounter, +import { + incrementWebSocketConnection, + decrementWebSocketConnection, + sessionAliveEventsCounter, machineAliveEventsCounter, websocketEventsCounter, httpRequestsCounter, httpRequestDurationHistogram } from "@/modules/metrics"; import { activityCache } from "@/modules/sessionCache"; +import { encryptBytes, encryptString } from "@/modules/encrypt"; +import { GitHubProfile } from "./types"; declare module 'fastify' { @@ -88,7 +90,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> // Increment request counter httpRequestsCounter.inc({ method, route, status }); - + // Record request duration httpRequestDurationHistogram.observe({ method, route, status }, duration); }); @@ -239,6 +241,143 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> return reply.send({ success: true }); }); + // GitHub OAuth parameters + typed.get('/v1/connect/github/params', { + preHandler: app.authenticate, + schema: { + response: { + 200: z.object({ + url: z.string() + }), + 400: z.object({ + error: z.string() + }), + 500: z.object({ + error: z.string() + }) + } + } + }, async (request, reply) => { + const clientId = process.env.GITHUB_CLIENT_ID; + const redirectUri = process.env.GITHUB_REDIRECT_URL; + + if (!clientId || !redirectUri) { + return reply.code(400).send({ error: 'GitHub OAuth not configured' }); + } + + // Generate ephemeral state token (5 minutes TTL) + const state = await auth.createGithubToken(request.userId); + + // Build complete OAuth URL + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: redirectUri, + scope: 'user', + state: state + }); + + const url = `https://github.com/login/oauth/authorize?${params.toString()}`; + + return reply.send({ url }); + }); + + // GitHub OAuth callback (GET for redirect from GitHub) + typed.get('/v1/connect/github/callback', { + schema: { + querystring: z.object({ + code: z.string(), + state: z.string() + }) + } + }, async (request, reply) => { + const { code, state } = request.query; + + // Verify the state token to get userId + const tokenData = await auth.verifyGithubToken(state); + if (!tokenData) { + return reply.redirect('https://app.happy.engineering?error=invalid_state'); + } + + const userId = tokenData.userId; + const clientId = process.env.GITHUB_CLIENT_ID; + const clientSecret = process.env.GITHUB_CLIENT_SECRET; + + if (!clientId || !clientSecret) { + return reply.redirect('https://app.happy.engineering?error=server_config'); + } + + try { + // Exchange code for access token + const tokenResponse = await fetch('https://github.com/login/oauth/access_token', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + client_id: clientId, + client_secret: clientSecret, + code: code + }) + }); + + const tokenResponseData = await tokenResponse.json() as { + access_token?: string; + error?: string; + error_description?: string; + }; + + if (tokenResponseData.error) { + return reply.redirect(`https://app.happy.engineering?error=${encodeURIComponent(tokenResponseData.error)}`); + } + + const accessToken = tokenResponseData.access_token; + + // Get user info from GitHub + const userResponse = await fetch('https://api.github.com/user', { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Accept': 'application/vnd.github.v3+json', + } + }); + + const userData = await userResponse.json() as GitHubProfile; + + if (!userResponse.ok) { + return reply.redirect('https://app.happy.engineering?error=github_user_fetch_failed'); + } + + // Store GitHub user and connect to account + const githubUser = await db.githubUser.upsert({ + where: { id: userData.id.toString() }, + update: { + profile: userData, + token: encryptString(['user', userId, 'github', 'token'], accessToken!) + }, + create: { + id: userData.id.toString(), + profile: userData, + token: encryptString(['user', userId, 'github', 'token'], accessToken!) + } + }); + + // Link GitHub user to account + await db.account.update({ + where: { id: userId }, + data: { githubUserId: githubUser.id } + }); + + log({ module: 'github-oauth' }, `GitHub account connected successfully for user ${userId}: ${userData.login}`); + + // Redirect to app with success + return reply.redirect(`https://app.happy.engineering?github=connected&user=${encodeURIComponent(userData.login)}`); + + } catch (error) { + log({ module: 'github-oauth' }, `Error in GitHub GET callback: ${error}`); + return reply.redirect('https://app.happy.engineering?error=server_error'); + } + }); + // Account auth request typed.post('/v1/auth/account/request', { schema: { @@ -331,11 +470,11 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> // Check if OpenAI API key is configured on server const OPENAI_API_KEY = process.env.OPENAI_API_KEY; if (!OPENAI_API_KEY) { - return reply.code(500).send({ - error: 'OpenAI API key not configured on server' + return reply.code(500).send({ + error: 'OpenAI API key not configured on server' }); } - + // Generate ephemeral token from OpenAI const response = await fetch('https://api.openai.com/v1/realtime/sessions', { method: 'POST', @@ -348,11 +487,11 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> voice: 'verse', }), }); - + if (!response.ok) { throw new Error(`OpenAI API error: ${response.status}`); } - + const data = await response.json() as { client_secret: { value: string; @@ -360,14 +499,14 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> }; id: string; }; - + return reply.send({ token: data.client_secret.value }); } catch (error) { log({ module: 'openai', level: 'error' }, 'Failed to generate ephemeral token', error); - return reply.code(500).send({ - error: 'Failed to generate ephemeral token' + return reply.code(500).send({ + error: 'Failed to generate ephemeral token' }); } }); @@ -642,11 +781,11 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> where: { id: request.userId }, select: { settings: true, settingsVersion: true } }); - + if (!user) { return reply.code(500).send({ error: 'Failed to get account settings' }); } - + return reply.send({ settings: user.settings, settingsVersion: user.settingsVersion @@ -690,14 +829,14 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> where: { id: userId }, select: { settings: true, settingsVersion: true } }); - + if (!currentUser) { return reply.code(500).send({ success: false, error: 'Failed to update account settings' }); } - + // Check current version if (currentUser.settingsVersion !== expectedVersion) { return reply.code(200).send({ @@ -1270,7 +1409,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> socket.on('disconnect', () => { websocketEventsCounter.inc({ event_type: 'disconnect' }); - + // Cleanup connections eventRouter.removeConnection(userId, connection); decrementWebSocketConnection(connection.connectionType); @@ -1319,7 +1458,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> // Track metrics websocketEventsCounter.inc({ event_type: 'session-alive' }); sessionAliveEventsCounter.inc(); - + // Basic validation if (!data || typeof data.time !== 'number' || !data.sid) { return; @@ -1364,7 +1503,7 @@ export async function startApi(): Promise<{ app: FastifyInstance; io: Server }> // Track metrics websocketEventsCounter.inc({ event_type: 'machine-alive' }); machineAliveEventsCounter.inc(); - + // Basic validation if (!data || typeof data.time !== 'number' || !data.machineId) { return; diff --git a/sources/app/types.ts b/sources/app/types.ts new file mode 100644 index 0000000..b2a064a --- /dev/null +++ b/sources/app/types.ts @@ -0,0 +1,39 @@ +export interface GitHubProfile { + id: number; + login: string; + type: string; + site_admin: boolean; + avatar_url: string; + gravatar_id: string | null; + name: string | null; + company: string | null; + blog: string | null; + location: string | null; + email: string | null; + hireable: boolean | null; + bio: string | null; + twitter_username: string | null; + public_repos: number; + public_gists: number; + followers: number; + following: number; + created_at: string; + updated_at: string; + // Private user fields (only available when authenticated) + private_gists?: number; + total_private_repos?: number; + owned_private_repos?: number; + disk_usage?: number; + collaborators?: number; + two_factor_authentication?: boolean; + plan?: { + collaborators: number; + name: string; + space: number; + private_repos: number; + }; +} + +export interface GitHubOrg { + +} \ No newline at end of file diff --git a/sources/dbtypes.ts b/sources/dbtypes.ts new file mode 100644 index 0000000..1879b59 --- /dev/null +++ b/sources/dbtypes.ts @@ -0,0 +1,8 @@ +import { GitHubProfile as GitHubProfileType, GitHubOrg as GitHubOrgType } from "./app/types"; + +declare global { + namespace PrismaJson { + type GitHubProfile = GitHubProfileType; + type GitHubOrg = GitHubOrgType; + } +} \ No newline at end of file diff --git a/sources/main.ts b/sources/main.ts index 98ff2a8..0872cfd 100644 --- a/sources/main.ts +++ b/sources/main.ts @@ -8,6 +8,8 @@ import { startMetricsServer } from "@/app/metrics"; import { activityCache } from "@/modules/sessionCache"; import { auth } from "./modules/auth"; import { startDatabaseMetricsUpdater } from "@/modules/metrics"; +import { initEncrypt } from "./modules/encrypt"; +import { initGithub } from "./modules/github"; async function main() { @@ -22,6 +24,8 @@ async function main() { await redis.ping(); // Initialize auth module + await initEncrypt(); + await initGithub(); await auth.init(); // diff --git a/sources/modules/auth.ts b/sources/modules/auth.ts index d9299ed..1b31349 100644 --- a/sources/modules/auth.ts +++ b/sources/modules/auth.ts @@ -10,6 +10,8 @@ interface TokenCacheEntry { interface AuthTokens { generator: Awaited>; verifier: Awaited>; + githubVerifier: Awaited>; + githubGenerator: Awaited>; } class AuthModule { @@ -27,13 +29,25 @@ class AuthModule { service: 'handy', seed: process.env.HANDY_MASTER_SECRET! }); + const verifier = await privacyKit.createPersistentTokenVerifier({ service: 'handy', publicKey: generator.publicKey }); - - this.tokens = { generator, verifier }; + + const githubVerifier = await privacyKit.createEphemeralTokenVerifier({ + service: 'github-happy', + publicKey: generator.publicKey, + }); + + const githubGenerator = await privacyKit.createEphemeralTokenGenerator({ + service: 'github-happy', + seed: process.env.HANDY_MASTER_SECRET!, + ttl: 5 * 60 * 1000 // 5 minutes + }); + + this.tokens = { generator, verifier, githubVerifier, githubGenerator }; log({ module: 'auth' }, 'Auth module initialized'); } @@ -133,6 +147,35 @@ class AuthModule { }; } + async createGithubToken(userId: string): Promise { + if (!this.tokens) { + throw new Error('Auth module not initialized'); + } + + const payload = { user: userId, purpose: 'github-oauth' }; + const token = await this.tokens.githubGenerator.new(payload); + + return token; + } + + async verifyGithubToken(token: string): Promise<{ userId: string } | null> { + if (!this.tokens) { + throw new Error('Auth module not initialized'); + } + + try { + const verified = await this.tokens.githubVerifier.verify(token); + if (!verified) { + return null; + } + + return { userId: verified.user as string }; + } catch (error) { + log({ module: 'auth', level: 'error' }, `GitHub token verification failed: ${error}`); + return null; + } + } + // Cleanup old entries (optional - can be called periodically) cleanup(): void { // Note: Since tokens are cached "forever" as requested, diff --git a/sources/modules/encrypt.ts b/sources/modules/encrypt.ts new file mode 100644 index 0000000..cde51a0 --- /dev/null +++ b/sources/modules/encrypt.ts @@ -0,0 +1,26 @@ +import { KeyTree, crypto } from "privacy-kit"; + +let keyTree: KeyTree | null = null; + +export async function initEncrypt() { + keyTree = new KeyTree(await crypto.deriveSecureKey({ + key: process.env.HANDY_MASTER_SECRET!, + usage: 'happy-server-tokens' + })); +} + +export function encryptString(path: string[], string: string) { + return keyTree!.symmetricEncrypt(path, string); +} + +export function encryptBytes(path: string[], bytes: Uint8Array) { + return keyTree!.symmetricEncrypt(path, bytes); +} + +export function decryptString(path: string[], encrypted: Uint8Array) { + return keyTree!.symmetricDecryptBuffer(path, encrypted); +} + +export function decryptBytes(path: string[], encrypted: Uint8Array) { + return keyTree!.symmetricDecryptBuffer(path, encrypted); +} \ No newline at end of file diff --git a/sources/modules/github.ts b/sources/modules/github.ts new file mode 100644 index 0000000..ece8bdc --- /dev/null +++ b/sources/modules/github.ts @@ -0,0 +1,19 @@ +import { App } from "octokit"; + +let app: App | null = null; + +export async function initGithub() { + if ( + process.env.GITHUB_APP_ID && + process.env.GITHUB_PRIVATE_KEY && + process.env.GITHUB_CLIENT_ID && + process.env.GITHUB_CLIENT_SECRET && + process.env.GITHUB_REDIRECT_URL && + process.env.GITHUB_WEBHOOK_SECRET + ) { + app = new App({ + appId: process.env.GITHUB_APP_ID, + privateKey: process.env.GITHUB_PRIVATE_KEY, + }); + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bcddcc0..ccdc5cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -381,6 +381,222 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== +"@octokit/app@^16.0.1": + version "16.1.0" + resolved "https://registry.yarnpkg.com/@octokit/app/-/app-16.1.0.tgz#e0d7472fc2e7ae7b0ab3f1e4cca8c2aba5f052ad" + integrity sha512-OdKHnm0CYLk8Setr47CATT4YnRTvWkpTYvE+B/l2B0mjszlfOIit3wqPHVslD2jfc1bD4UbO7Mzh6gjCuMZKsA== + dependencies: + "@octokit/auth-app" "^8.1.0" + "@octokit/auth-unauthenticated" "^7.0.1" + "@octokit/core" "^7.0.2" + "@octokit/oauth-app" "^8.0.1" + "@octokit/plugin-paginate-rest" "^13.0.0" + "@octokit/types" "^14.0.0" + "@octokit/webhooks" "^14.0.0" + +"@octokit/auth-app@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-app/-/auth-app-8.1.0.tgz#19dacfe49ea62a1f40189ac222925e8a757b6a7c" + integrity sha512-6bWhyvLXqCSfHiqlwzn9pScLZ+Qnvh/681GR/UEEPCMIVwfpRDBw0cCzy3/t2Dq8B7W2X/8pBgmw6MOiyE0DXQ== + dependencies: + "@octokit/auth-oauth-app" "^9.0.1" + "@octokit/auth-oauth-user" "^6.0.0" + "@octokit/request" "^10.0.2" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + toad-cache "^3.7.0" + universal-github-app-jwt "^2.2.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-app@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.1.tgz#d8d2e950c95e9fcbe6f2fb98d4539ee8c9871766" + integrity sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g== + dependencies: + "@octokit/auth-oauth-device" "^8.0.1" + "@octokit/auth-oauth-user" "^6.0.0" + "@octokit/request" "^10.0.2" + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-device@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.1.tgz#232ec13e299dd6bf199fe237527d04ec12decffb" + integrity sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw== + dependencies: + "@octokit/oauth-methods" "^6.0.0" + "@octokit/request" "^10.0.2" + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-oauth-user@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.0.tgz#ba643060824536cd848c72d835061b1c00007286" + integrity sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A== + dependencies: + "@octokit/auth-oauth-device" "^8.0.1" + "@octokit/oauth-methods" "^6.0.0" + "@octokit/request" "^10.0.2" + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.0" + +"@octokit/auth-token@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-6.0.0.tgz#b02e9c08a2d8937df09a2a981f226ad219174c53" + integrity sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w== + +"@octokit/auth-unauthenticated@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.1.tgz#427b8a52e672318f84757307e766a448c741116b" + integrity sha512-qVq1vdjLLZdE8kH2vDycNNjuJRCD1q2oet1nA/GXWaYlpDxlR7rdVhX/K/oszXslXiQIiqrQf+rdhDlA99JdTQ== + dependencies: + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + +"@octokit/core@^7.0.2": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-7.0.3.tgz#0b5288995fed66920128d41cfeea34979d48a360" + integrity sha512-oNXsh2ywth5aowwIa7RKtawnkdH6LgU1ztfP9AIUCQCvzysB+WeU8o2kyyosDPwBZutPpjZDKPQGIzzrfTWweQ== + dependencies: + "@octokit/auth-token" "^6.0.0" + "@octokit/graphql" "^9.0.1" + "@octokit/request" "^10.0.2" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + before-after-hook "^4.0.0" + universal-user-agent "^7.0.0" + +"@octokit/endpoint@^11.0.0": + version "11.0.0" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-11.0.0.tgz#189fcc022721b4c49d0307eea6be3de1cfb53026" + integrity sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ== + dependencies: + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.2" + +"@octokit/graphql@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-9.0.1.tgz#eb258fc9981403d2d751720832652c385b6c1613" + integrity sha512-j1nQNU1ZxNFx2ZtKmL4sMrs4egy5h65OMDmSbVyuCzjOcwsHq6EaYjOTGXPQxgfiN8dJ4CriYHk6zF050WEULg== + dependencies: + "@octokit/request" "^10.0.2" + "@octokit/types" "^14.0.0" + universal-user-agent "^7.0.0" + +"@octokit/oauth-app@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@octokit/oauth-app/-/oauth-app-8.0.1.tgz#7cb889945c3ccacfd0ff9aa9f05e9748b439f7f3" + integrity sha512-QnhMYEQpnYbEPn9cae+wXL2LuPMFglmfeuDJXXsyxIXdoORwkLK8y0cHhd/5du9MbO/zdG/BXixzB7EEwU63eQ== + dependencies: + "@octokit/auth-oauth-app" "^9.0.1" + "@octokit/auth-oauth-user" "^6.0.0" + "@octokit/auth-unauthenticated" "^7.0.1" + "@octokit/core" "^7.0.2" + "@octokit/oauth-authorization-url" "^8.0.0" + "@octokit/oauth-methods" "^6.0.0" + "@types/aws-lambda" "^8.10.83" + universal-user-agent "^7.0.0" + +"@octokit/oauth-authorization-url@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz#fdbab39a07d38faaad8621a5fdf04bc0c36d63e7" + integrity sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ== + +"@octokit/oauth-methods@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/oauth-methods/-/oauth-methods-6.0.0.tgz#a138bbbec6762b52249f7c47d0c548fc1cf6ad7b" + integrity sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA== + dependencies: + "@octokit/oauth-authorization-url" "^8.0.0" + "@octokit/request" "^10.0.2" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + +"@octokit/openapi-types@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-25.1.0.tgz#5a72a9dfaaba72b5b7db375fd05e90ca90dc9682" + integrity sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA== + +"@octokit/openapi-webhooks-types@12.0.3": + version "12.0.3" + resolved "https://registry.yarnpkg.com/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.0.3.tgz#fa44fb31fbb4c444c5fd640dbbf537f00c20617c" + integrity sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA== + +"@octokit/plugin-paginate-graphql@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz#acdefd7e85ce24716e7ad7352f2df4d29d0e273b" + integrity sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ== + +"@octokit/plugin-paginate-rest@^13.0.0": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.1.1.tgz#ca5bb1c7b85a583691263c1f788f607e9bcb74b3" + integrity sha512-q9iQGlZlxAVNRN2jDNskJW/Cafy7/XE52wjZ5TTvyhyOD904Cvx//DNyoO3J/MXJ0ve3rPoNWKEg5iZrisQSuw== + dependencies: + "@octokit/types" "^14.1.0" + +"@octokit/plugin-rest-endpoint-methods@^16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.0.0.tgz#ba30ca387fc2ac8bd93cf9f951174736babebd97" + integrity sha512-kJVUQk6/dx/gRNLWUnAWKFs1kVPn5O5CYZyssyEoNYaFedqZxsfYs7DwI3d67hGz4qOwaJ1dpm07hOAD1BXx6g== + dependencies: + "@octokit/types" "^14.1.0" + +"@octokit/plugin-retry@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-8.0.1.tgz#ee4a0487d31b97ad3deaf737faad68abeca3c227" + integrity sha512-KUoYR77BjF5O3zcwDQHRRZsUvJwepobeqiSSdCJ8lWt27FZExzb0GgVxrhhfuyF6z2B2zpO0hN5pteni1sqWiw== + dependencies: + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + bottleneck "^2.15.3" + +"@octokit/plugin-throttling@^11.0.1": + version "11.0.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-throttling/-/plugin-throttling-11.0.1.tgz#31a0b5e759f0313514d9522a4103360f17ffc2e4" + integrity sha512-S+EVhy52D/272L7up58dr3FNSMXWuNZolkL4zMJBNIfIxyZuUcczsQAU4b5w6dewJXnKYVgSHSV5wxitMSW1kw== + dependencies: + "@octokit/types" "^14.0.0" + bottleneck "^2.15.3" + +"@octokit/request-error@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-7.0.0.tgz#48ae2cd79008315605d00e83664891a10a5ddb97" + integrity sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg== + dependencies: + "@octokit/types" "^14.0.0" + +"@octokit/request@^10.0.2": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-10.0.3.tgz#2ffdb88105ce20d25dcab8a592a7040ea48306c7" + integrity sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA== + dependencies: + "@octokit/endpoint" "^11.0.0" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + fast-content-type-parse "^3.0.0" + universal-user-agent "^7.0.2" + +"@octokit/types@^14.0.0", "@octokit/types@^14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-14.1.0.tgz#3bf9b3a3e3b5270964a57cc9d98592ed44f840f2" + integrity sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g== + dependencies: + "@octokit/openapi-types" "^25.1.0" + +"@octokit/webhooks-methods@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz#34abf78aec6f826fe561cfe79d2ebb1950d1d25f" + integrity sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ== + +"@octokit/webhooks@^14.0.0": + version "14.1.3" + resolved "https://registry.yarnpkg.com/@octokit/webhooks/-/webhooks-14.1.3.tgz#2d0bed71b07745c0b33363d69e0ae0e440469a18" + integrity sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ== + dependencies: + "@octokit/openapi-webhooks-types" "12.0.3" + "@octokit/request-error" "^7.0.0" + "@octokit/webhooks-methods" "^6.0.0" + "@opentelemetry/api@^1.4.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" @@ -719,6 +935,11 @@ resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/aws-lambda@^8.10.83": + version "8.10.152" + resolved "https://registry.yarnpkg.com/@types/aws-lambda/-/aws-lambda-8.10.152.tgz#f68424a8175f0a54a2a941e65b76c3f51f3bd89d" + integrity sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw== + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -1029,11 +1250,21 @@ base64id@2.0.0, base64id@~2.0.0: resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== +before-after-hook@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-4.0.0.tgz#cf1447ab9160df6a40f3621da64d6ffc36050cb9" + integrity sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ== + bintrees@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.2.tgz#49f896d6e858a4a499df85c38fb399b9aff840f8" integrity sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw== +bottleneck@^2.15.3: + version "2.19.5" + resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" + integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== + buffer-equal-constant-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -1431,6 +1662,11 @@ expect-type@^1.2.1: resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.2.1.tgz#af76d8b357cf5fa76c41c09dafb79c549e75f71f" integrity sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw== +fast-content-type-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz#5590b6c807cc598be125e6740a9fde589d2b7afb" + integrity sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg== + fast-copy@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35" @@ -1903,6 +2139,23 @@ obliterator@^2.0.4: resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-2.0.5.tgz#031e0145354b0c18840336ae51d41e7d6d2c76aa" integrity sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw== +octokit@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/octokit/-/octokit-5.0.3.tgz#1e4f110e28218ab9676c28da5f28ab403fe5b643" + integrity sha512-+bwYsAIRmYv30NTmBysPIlgH23ekVDriB07oRxlPIAH5PI0yTMSxg5i5Xy0OetcnZw+nk/caD4szD7a9YZ3QyQ== + dependencies: + "@octokit/app" "^16.0.1" + "@octokit/core" "^7.0.2" + "@octokit/oauth-app" "^8.0.1" + "@octokit/plugin-paginate-graphql" "^6.0.0" + "@octokit/plugin-paginate-rest" "^13.0.0" + "@octokit/plugin-rest-endpoint-methods" "^16.0.0" + "@octokit/plugin-retry" "^8.0.1" + "@octokit/plugin-throttling" "^11.0.1" + "@octokit/request-error" "^7.0.0" + "@octokit/types" "^14.0.0" + "@octokit/webhooks" "^14.0.0" + on-exit-leak-free@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" @@ -2503,6 +2756,16 @@ undici-types@~7.8.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.8.0.tgz#de00b85b710c54122e44fbfd911f8d70174cd294" integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== +universal-github-app-jwt@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz#38537e5a7d154085a35f97601a5e30e9e17717df" + integrity sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw== + +universal-user-agent@^7.0.0, universal-user-agent@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz#c05870a58125a2dc00431f2df815a77fe69736be" + integrity sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A== + url-join@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"