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-pino": "^0.10.2",
|
||||
"lodash-es": "catalog:",
|
||||
"octokit": "catalog:",
|
||||
"pino": "^9.9.1",
|
||||
"pino-pretty": "^13.1.1",
|
||||
"tsup": "catalog:",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
getActiveGithubIntegration,
|
||||
getGithubIntegrationByInstallationId,
|
||||
updateGithubIntegration,
|
||||
} from '@buster/database';
|
||||
import { getActiveGithubIntegration, getGithubIntegrationByInstallationId } from '@buster/database';
|
||||
import type { InstallationTokenResponse } 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
|
||||
|
@ -80,61 +80,6 @@ export async function getInstallationTokenByOrgId(
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* This module exports all GitHub-related service functions
|
||||
* This module exports GitHub-related service functions
|
||||
* for handling GitHub App installations and tokens
|
||||
*/
|
||||
|
||||
// GitHub App configuration and creation
|
||||
export {
|
||||
createGitHubApp,
|
||||
getGitHubAppCredentials,
|
||||
} from './github-app';
|
||||
|
||||
// Installation webhook handling
|
||||
export { handleInstallationCallback } from './handle-installation-callback';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createHmac, timingSafeEqual } from 'node:crypto';
|
||||
import { getGitHubAppCredentials } from '@buster/github';
|
||||
import { GitHubErrorCode } from '@buster/server-shared/github';
|
||||
import { getGitHubAppCredentials } from './github-app';
|
||||
|
||||
/**
|
||||
* Verify a GitHub webhook signature
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"@buster/env-utils": "workspace:*",
|
||||
"@buster/database": "workspace:*",
|
||||
"@buster/server-shared": "workspace:*",
|
||||
"octokit": "catalog:",
|
||||
"octokit": "^5.0.3",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { App } from 'octokit';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createGitHubApp, getGitHubAppCredentials } from './github-app';
|
||||
import { createGitHubApp, getGitHubAppCredentials } from './app';
|
||||
|
||||
// Mock the octokit module
|
||||
vi.mock('octokit', () => ({
|
||||
|
@ -98,7 +98,7 @@ describe('github-app', () => {
|
|||
|
||||
// Act & Assert
|
||||
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,
|
||||
isTokenExpired,
|
||||
generateTokenVaultKey,
|
||||
generateNewInstallationToken,
|
||||
} from './services/token';
|
||||
|
||||
export {
|
||||
|
|
|
@ -229,7 +229,7 @@ export async function getInstallationTokenByOrgId(
|
|||
/**
|
||||
* Generate a new installation token from GitHub
|
||||
*/
|
||||
async function generateNewInstallationToken(
|
||||
export async function generateNewInstallationToken(
|
||||
installationId: string,
|
||||
integrationId: string
|
||||
): Promise<InstallationTokenResponse> {
|
||||
|
|
|
@ -42,9 +42,6 @@ catalogs:
|
|||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
octokit:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
pg:
|
||||
specifier: ^8.16.3
|
||||
version: 8.16.3
|
||||
|
@ -181,9 +178,6 @@ importers:
|
|||
lodash-es:
|
||||
specifier: 'catalog:'
|
||||
version: 4.17.21
|
||||
octokit:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.3
|
||||
pino:
|
||||
specifier: ^9.9.1
|
||||
version: 9.9.1
|
||||
|
@ -1029,7 +1023,7 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../vitest-config
|
||||
octokit:
|
||||
specifier: 'catalog:'
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
|
|
|
@ -20,7 +20,6 @@ catalog:
|
|||
drizzle-orm: ^0.44.5
|
||||
lodash-es: ^4.17.21
|
||||
hono: ^4.9.6
|
||||
octokit: ^5.0.3
|
||||
pg: ^8.16.3
|
||||
tsup: ^8.5.0
|
||||
tsx: ^4.20.5
|
||||
|
|
Loading…
Reference in New Issue