mirror of https://github.com/buster-so/buster.git
simplify the server build
This commit is contained in:
parent
97e00e7b6b
commit
ae19b51ecc
|
@ -44,7 +44,6 @@
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"hono-pino": "^0.10.2",
|
"hono-pino": "^0.10.2",
|
||||||
"lodash-es": "catalog:",
|
"lodash-es": "catalog:",
|
||||||
"octokit": "catalog:",
|
|
||||||
"pino": "^9.9.1",
|
"pino": "^9.9.1",
|
||||||
"pino-pretty": "^13.1.1",
|
"pino-pretty": "^13.1.1",
|
||||||
"tsup": "catalog:",
|
"tsup": "catalog:",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {
|
import { getActiveGithubIntegration, getGithubIntegrationByInstallationId } from '@buster/database';
|
||||||
getActiveGithubIntegration,
|
|
||||||
getGithubIntegrationByInstallationId,
|
|
||||||
updateGithubIntegration,
|
|
||||||
} from '@buster/database';
|
|
||||||
import type { InstallationTokenResponse } from '@buster/server-shared/github';
|
import type { InstallationTokenResponse } from '@buster/server-shared/github';
|
||||||
import { GitHubErrorCode } from '@buster/server-shared/github';
|
import { GitHubErrorCode } from '@buster/server-shared/github';
|
||||||
import { createGitHubApp } from './github-app';
|
|
||||||
import { isTokenExpired, retrieveInstallationToken, storeInstallationToken } from './token-storage';
|
import {
|
||||||
|
generateNewInstallationToken,
|
||||||
|
isTokenExpired,
|
||||||
|
retrieveInstallationToken,
|
||||||
|
} from '@buster/github';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an installation token for a specific installation ID
|
* Get an installation token for a specific installation ID
|
||||||
|
@ -80,61 +80,6 @@ export async function getInstallationTokenByOrgId(
|
||||||
return await getInstallationToken(integration.installationId);
|
return await getInstallationToken(integration.installationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a new installation token from GitHub
|
|
||||||
*/
|
|
||||||
async function generateNewInstallationToken(
|
|
||||||
installationId: string,
|
|
||||||
integrationId: string
|
|
||||||
): Promise<InstallationTokenResponse> {
|
|
||||||
try {
|
|
||||||
const app = createGitHubApp();
|
|
||||||
|
|
||||||
// Create installation access token
|
|
||||||
const { data } = await app.octokit.rest.apps.createInstallationAccessToken({
|
|
||||||
installation_id: Number.parseInt(installationId, 10),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store the new token in vault
|
|
||||||
const vaultKey = await storeInstallationToken(
|
|
||||||
installationId,
|
|
||||||
data.token,
|
|
||||||
data.expires_at,
|
|
||||||
data.permissions,
|
|
||||||
data.repository_selection
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the integration with the new vault key
|
|
||||||
await updateGithubIntegration(integrationId, {
|
|
||||||
tokenVaultKey: vaultKey,
|
|
||||||
status: 'active', // Ensure status is active after successful token generation
|
|
||||||
});
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
`Generated new token for installation ${installationId}, expires at ${data.expires_at}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
token: data.token,
|
|
||||||
expires_at: data.expires_at,
|
|
||||||
permissions: data.permissions,
|
|
||||||
repository_selection: data.repository_selection,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to generate token for installation ${installationId}:`, error);
|
|
||||||
|
|
||||||
// If token generation fails, mark the integration as failed
|
|
||||||
await updateGithubIntegration(integrationId, {
|
|
||||||
status: 'suspended',
|
|
||||||
});
|
|
||||||
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.TOKEN_GENERATION_FAILED,
|
|
||||||
`Failed to generate token: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that an installation belongs to a specific organization
|
* Verify that an installation belongs to a specific organization
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
import type { GitHubOperationError } from '@buster/server-shared/github';
|
|
||||||
import { GitHubErrorCode } from '@buster/server-shared/github';
|
|
||||||
import { App } from 'octokit';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get GitHub App credentials from environment variables
|
|
||||||
*/
|
|
||||||
export function getGitHubAppCredentials(): {
|
|
||||||
appId: number;
|
|
||||||
privateKey: string;
|
|
||||||
webhookSecret: string;
|
|
||||||
} {
|
|
||||||
const appId = process.env.GITHUB_APP_ID;
|
|
||||||
const privateKeyBase64 = process.env.GITHUB_APP_PRIVATE_KEY_BASE64;
|
|
||||||
const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
||||||
|
|
||||||
if (!appId) {
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.APP_CONFIGURATION_ERROR,
|
|
||||||
'GITHUB_APP_ID environment variable is not set'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!privateKeyBase64) {
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.APP_CONFIGURATION_ERROR,
|
|
||||||
'GITHUB_APP_PRIVATE_KEY_BASE64 environment variable is not set'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!webhookSecret) {
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.APP_CONFIGURATION_ERROR,
|
|
||||||
'GITHUB_WEBHOOK_SECRET environment variable is not set'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the private key from base64
|
|
||||||
let privateKey: string;
|
|
||||||
try {
|
|
||||||
// Check if it's valid base64 first (allow whitespace which will be trimmed)
|
|
||||||
const trimmedBase64 = privateKeyBase64.trim();
|
|
||||||
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(trimmedBase64)) {
|
|
||||||
throw new Error('Invalid base64 format');
|
|
||||||
}
|
|
||||||
privateKey = Buffer.from(trimmedBase64, 'base64').toString('utf-8');
|
|
||||||
} catch (_error) {
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.APP_CONFIGURATION_ERROR,
|
|
||||||
'Failed to decode GITHUB_APP_PRIVATE_KEY_BASE64: Invalid base64 encoding'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the private key format (support both RSA and PKCS#8 formats)
|
|
||||||
if (!privateKey.includes('BEGIN RSA PRIVATE KEY') && !privateKey.includes('BEGIN PRIVATE KEY')) {
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.APP_CONFIGURATION_ERROR,
|
|
||||||
'Invalid GitHub App private key format. Expected PEM-encoded RSA private key or PKCS#8 private key'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
appId: Number.parseInt(appId, 10),
|
|
||||||
privateKey,
|
|
||||||
webhookSecret,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a configured GitHub App instance
|
|
||||||
*/
|
|
||||||
export function createGitHubApp(): App {
|
|
||||||
const { appId, privateKey } = getGitHubAppCredentials();
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new App({
|
|
||||||
appId,
|
|
||||||
privateKey,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw createGitHubError(
|
|
||||||
GitHubErrorCode.APP_CONFIGURATION_ERROR,
|
|
||||||
`Failed to create GitHub App: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a GitHub operation error
|
|
||||||
*/
|
|
||||||
function createGitHubError(code: GitHubErrorCode, message: string): Error {
|
|
||||||
const error = new Error(message) as Error & GitHubOperationError;
|
|
||||||
error.code = code;
|
|
||||||
return error;
|
|
||||||
}
|
|
|
@ -1,16 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* GitHub Services
|
* GitHub Services
|
||||||
*
|
*
|
||||||
* This module exports all GitHub-related service functions
|
* This module exports GitHub-related service functions
|
||||||
* for handling GitHub App installations and tokens
|
* for handling GitHub App installations and tokens
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// GitHub App configuration and creation
|
|
||||||
export {
|
|
||||||
createGitHubApp,
|
|
||||||
getGitHubAppCredentials,
|
|
||||||
} from './github-app';
|
|
||||||
|
|
||||||
// Installation webhook handling
|
// Installation webhook handling
|
||||||
export { handleInstallationCallback } from './handle-installation-callback';
|
export { handleInstallationCallback } from './handle-installation-callback';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createHmac, timingSafeEqual } from 'node:crypto';
|
import { createHmac, timingSafeEqual } from 'node:crypto';
|
||||||
|
import { getGitHubAppCredentials } from '@buster/github';
|
||||||
import { GitHubErrorCode } from '@buster/server-shared/github';
|
import { GitHubErrorCode } from '@buster/server-shared/github';
|
||||||
import { getGitHubAppCredentials } from './github-app';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify a GitHub webhook signature
|
* Verify a GitHub webhook signature
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
"@buster/env-utils": "workspace:*",
|
"@buster/env-utils": "workspace:*",
|
||||||
"@buster/database": "workspace:*",
|
"@buster/database": "workspace:*",
|
||||||
"@buster/server-shared": "workspace:*",
|
"@buster/server-shared": "workspace:*",
|
||||||
"octokit": "catalog:",
|
"octokit": "^5.0.3",
|
||||||
"zod": "catalog:"
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { App } from 'octokit';
|
import { App } from 'octokit';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { createGitHubApp, getGitHubAppCredentials } from './github-app';
|
import { createGitHubApp, getGitHubAppCredentials } from './app';
|
||||||
|
|
||||||
// Mock the octokit module
|
// Mock the octokit module
|
||||||
vi.mock('octokit', () => ({
|
vi.mock('octokit', () => ({
|
||||||
|
@ -98,7 +98,7 @@ describe('github-app', () => {
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
expect(() => getGitHubAppCredentials()).toThrow(
|
expect(() => getGitHubAppCredentials()).toThrow(
|
||||||
'Invalid GitHub App private key format. Expected PEM-encoded RSA private key or PKCS#8 private key'
|
'Invalid GitHub App private key format. Expected PEM-encoded private key'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -11,6 +11,7 @@ export {
|
||||||
deleteInstallationToken,
|
deleteInstallationToken,
|
||||||
isTokenExpired,
|
isTokenExpired,
|
||||||
generateTokenVaultKey,
|
generateTokenVaultKey,
|
||||||
|
generateNewInstallationToken,
|
||||||
} from './services/token';
|
} from './services/token';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -229,7 +229,7 @@ export async function getInstallationTokenByOrgId(
|
||||||
/**
|
/**
|
||||||
* Generate a new installation token from GitHub
|
* Generate a new installation token from GitHub
|
||||||
*/
|
*/
|
||||||
async function generateNewInstallationToken(
|
export async function generateNewInstallationToken(
|
||||||
installationId: string,
|
installationId: string,
|
||||||
integrationId: string
|
integrationId: string
|
||||||
): Promise<InstallationTokenResponse> {
|
): Promise<InstallationTokenResponse> {
|
||||||
|
|
|
@ -42,9 +42,6 @@ catalogs:
|
||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
octokit:
|
|
||||||
specifier: ^5.0.3
|
|
||||||
version: 5.0.3
|
|
||||||
pg:
|
pg:
|
||||||
specifier: ^8.16.3
|
specifier: ^8.16.3
|
||||||
version: 8.16.3
|
version: 8.16.3
|
||||||
|
@ -181,9 +178,6 @@ importers:
|
||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
octokit:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 5.0.3
|
|
||||||
pino:
|
pino:
|
||||||
specifier: ^9.9.1
|
specifier: ^9.9.1
|
||||||
version: 9.9.1
|
version: 9.9.1
|
||||||
|
@ -1029,7 +1023,7 @@ importers:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../vitest-config
|
version: link:../vitest-config
|
||||||
octokit:
|
octokit:
|
||||||
specifier: 'catalog:'
|
specifier: ^5.0.3
|
||||||
version: 5.0.3
|
version: 5.0.3
|
||||||
zod:
|
zod:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
|
|
|
@ -20,7 +20,6 @@ catalog:
|
||||||
drizzle-orm: ^0.44.5
|
drizzle-orm: ^0.44.5
|
||||||
lodash-es: ^4.17.21
|
lodash-es: ^4.17.21
|
||||||
hono: ^4.9.6
|
hono: ^4.9.6
|
||||||
octokit: ^5.0.3
|
|
||||||
pg: ^8.16.3
|
pg: ^8.16.3
|
||||||
tsup: ^8.5.0
|
tsup: ^8.5.0
|
||||||
tsx: ^4.20.5
|
tsx: ^4.20.5
|
||||||
|
|
Loading…
Reference in New Issue