@flink-app/github-app-plugin 0.12.1-alpha.38
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/CHANGELOG.md +209 -0
- package/LICENSE +21 -0
- package/README.md +667 -0
- package/SECURITY.md +498 -0
- package/dist/GitHubAppInternalContext.d.ts +44 -0
- package/dist/GitHubAppInternalContext.js +2 -0
- package/dist/GitHubAppPlugin.d.ts +45 -0
- package/dist/GitHubAppPlugin.js +367 -0
- package/dist/GitHubAppPluginContext.d.ts +242 -0
- package/dist/GitHubAppPluginContext.js +2 -0
- package/dist/GitHubAppPluginOptions.d.ts +369 -0
- package/dist/GitHubAppPluginOptions.js +2 -0
- package/dist/handlers/InitiateInstallation.d.ts +32 -0
- package/dist/handlers/InitiateInstallation.js +66 -0
- package/dist/handlers/InstallationCallback.d.ts +42 -0
- package/dist/handlers/InstallationCallback.js +248 -0
- package/dist/handlers/UninstallHandler.d.ts +37 -0
- package/dist/handlers/UninstallHandler.js +153 -0
- package/dist/handlers/WebhookHandler.d.ts +54 -0
- package/dist/handlers/WebhookHandler.js +157 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +23 -0
- package/dist/repos/GitHubAppSessionRepo.d.ts +24 -0
- package/dist/repos/GitHubAppSessionRepo.js +32 -0
- package/dist/repos/GitHubInstallationRepo.d.ts +53 -0
- package/dist/repos/GitHubInstallationRepo.js +83 -0
- package/dist/repos/GitHubWebhookEventRepo.d.ts +29 -0
- package/dist/repos/GitHubWebhookEventRepo.js +42 -0
- package/dist/schemas/GitHubAppSession.d.ts +13 -0
- package/dist/schemas/GitHubAppSession.js +2 -0
- package/dist/schemas/GitHubInstallation.d.ts +28 -0
- package/dist/schemas/GitHubInstallation.js +2 -0
- package/dist/schemas/InstallationCallbackRequest.d.ts +10 -0
- package/dist/schemas/InstallationCallbackRequest.js +2 -0
- package/dist/schemas/WebhookEvent.d.ts +16 -0
- package/dist/schemas/WebhookEvent.js +2 -0
- package/dist/schemas/WebhookPayload.d.ts +35 -0
- package/dist/schemas/WebhookPayload.js +2 -0
- package/dist/services/GitHubAPIClient.d.ts +143 -0
- package/dist/services/GitHubAPIClient.js +167 -0
- package/dist/services/GitHubAuthService.d.ts +85 -0
- package/dist/services/GitHubAuthService.js +160 -0
- package/dist/services/WebhookValidator.d.ts +93 -0
- package/dist/services/WebhookValidator.js +123 -0
- package/dist/utils/error-utils.d.ts +67 -0
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/jwt-utils.d.ts +35 -0
- package/dist/utils/jwt-utils.js +67 -0
- package/dist/utils/state-utils.d.ts +38 -0
- package/dist/utils/state-utils.js +74 -0
- package/dist/utils/token-cache-utils.d.ts +47 -0
- package/dist/utils/token-cache-utils.js +74 -0
- package/dist/utils/webhook-signature-utils.d.ts +22 -0
- package/dist/utils/webhook-signature-utils.js +57 -0
- package/examples/basic-installation.ts +246 -0
- package/examples/create-issue.ts +392 -0
- package/examples/error-handling.ts +396 -0
- package/examples/multi-event-webhook.ts +367 -0
- package/examples/organization-installation.ts +316 -0
- package/examples/repository-access.ts +480 -0
- package/examples/webhook-handling.ts +343 -0
- package/examples/with-jwt-auth.ts +319 -0
- package/package.json +41 -0
- package/spec/core-utilities.spec.ts +243 -0
- package/spec/handlers.spec.ts +216 -0
- package/spec/helpers/reporter.ts +41 -0
- package/spec/integration-and-security.spec.ts +483 -0
- package/spec/plugin-core.spec.ts +258 -0
- package/spec/project-setup.spec.ts +56 -0
- package/spec/repos-and-schemas.spec.ts +288 -0
- package/spec/services.spec.ts +108 -0
- package/spec/support/jasmine.json +7 -0
- package/src/GitHubAppPlugin.ts +411 -0
- package/src/GitHubAppPluginContext.ts +254 -0
- package/src/GitHubAppPluginOptions.ts +412 -0
- package/src/handlers/InstallationCallback.ts +292 -0
- package/src/handlers/WebhookHandler.ts +179 -0
- package/src/index.ts +29 -0
- package/src/repos/GitHubAppSessionRepo.ts +36 -0
- package/src/repos/GitHubInstallationRepo.ts +95 -0
- package/src/repos/GitHubWebhookEventRepo.ts +48 -0
- package/src/schemas/GitHubAppSession.ts +13 -0
- package/src/schemas/GitHubInstallation.ts +28 -0
- package/src/schemas/InstallationCallbackRequest.ts +10 -0
- package/src/schemas/WebhookEvent.ts +16 -0
- package/src/schemas/WebhookPayload.ts +35 -0
- package/src/services/GitHubAPIClient.ts +244 -0
- package/src/services/GitHubAuthService.ts +188 -0
- package/src/services/WebhookValidator.ts +159 -0
- package/src/utils/error-utils.ts +148 -0
- package/src/utils/jwt-utils.ts +64 -0
- package/src/utils/state-utils.ts +72 -0
- package/src/utils/token-cache-utils.ts +89 -0
- package/src/utils/webhook-signature-utils.ts +57 -0
- package/tsconfig.dist.json +4 -0
- package/tsconfig.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
# GitHub App Plugin
|
|
2
|
+
|
|
3
|
+
A standalone Flink plugin for GitHub App integration with installation management, JWT-based authentication, webhook handling with signature validation, and GitHub API client wrapper.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- GitHub App installation flow with CSRF protection
|
|
8
|
+
- Automatic JWT signing with RSA private key (RS256 algorithm)
|
|
9
|
+
- Installation access token management with automatic caching and refresh
|
|
10
|
+
- Webhook integration with HMAC-SHA256 signature validation
|
|
11
|
+
- GitHub API client wrapper with automatic token injection
|
|
12
|
+
- Repository access verification
|
|
13
|
+
- Standalone plugin (works with any authentication system)
|
|
14
|
+
- TypeScript support with full type safety
|
|
15
|
+
- Auto-detection of PKCS#1 and PKCS#8 private key formats
|
|
16
|
+
- Configurable MongoDB collections and TTL settings
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @flink-app/github-app-plugin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
### 1. GitHub App Setup
|
|
27
|
+
|
|
28
|
+
You need to create a GitHub App to use this plugin:
|
|
29
|
+
|
|
30
|
+
1. Go to [GitHub Settings > Developer settings > GitHub Apps](https://github.com/settings/apps)
|
|
31
|
+
2. Click "New GitHub App"
|
|
32
|
+
3. Fill in the required fields:
|
|
33
|
+
- **App Name:** Your app name (e.g., "My Flink App")
|
|
34
|
+
- **Homepage URL:** Your application URL
|
|
35
|
+
- **Webhook URL:** `https://yourdomain.com/github-app/webhook`
|
|
36
|
+
- **Webhook Secret:** Generate a secure random string (save this!)
|
|
37
|
+
4. Set **Repository permissions** based on your needs:
|
|
38
|
+
- Contents: Read or Write
|
|
39
|
+
- Issues: Read or Write
|
|
40
|
+
- Pull requests: Read or Write
|
|
41
|
+
- etc.
|
|
42
|
+
5. Subscribe to **Webhook events**:
|
|
43
|
+
- Push
|
|
44
|
+
- Pull request
|
|
45
|
+
- Issues
|
|
46
|
+
- Installation
|
|
47
|
+
- etc.
|
|
48
|
+
6. Click "Create GitHub App"
|
|
49
|
+
7. After creation:
|
|
50
|
+
- Note the **App ID**
|
|
51
|
+
- Note the **Client ID**
|
|
52
|
+
- Generate and download the **private key** (PEM file)
|
|
53
|
+
- Generate and save the **Client Secret**
|
|
54
|
+
- Note the **App Slug** (optional, used in installation URL)
|
|
55
|
+
|
|
56
|
+
### 2. Configure Private Key
|
|
57
|
+
|
|
58
|
+
The plugin requires your GitHub App's private key in **Base64 encoded** format to avoid issues with line breaks in environment variables.
|
|
59
|
+
|
|
60
|
+
**Encode your private key to base64:**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# On macOS/Linux:
|
|
64
|
+
base64 -i github-app-private-key.pem | tr -d '\n'
|
|
65
|
+
|
|
66
|
+
# On Windows (PowerShell):
|
|
67
|
+
[Convert]::ToBase64String([IO.File]::ReadAllBytes("github-app-private-key.pem"))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Store the base64 encoded key in an environment variable:**
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# .env
|
|
74
|
+
GITHUB_APP_PRIVATE_KEY_BASE64="LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVB..."
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Important:** Never commit your private key to version control!
|
|
78
|
+
|
|
79
|
+
### 3. MongoDB Connection
|
|
80
|
+
|
|
81
|
+
The plugin requires MongoDB to store installation data and sessions.
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { FlinkApp } from "@flink-app/flink";
|
|
87
|
+
import { githubAppPlugin } from "@flink-app/github-app-plugin";
|
|
88
|
+
import { Context } from "./Context";
|
|
89
|
+
|
|
90
|
+
const app = new FlinkApp<Context>({
|
|
91
|
+
name: "My App",
|
|
92
|
+
|
|
93
|
+
db: {
|
|
94
|
+
uri: process.env.MONGODB_URI!,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
plugins: [
|
|
98
|
+
githubAppPlugin({
|
|
99
|
+
// GitHub App credentials (required)
|
|
100
|
+
appId: process.env.GITHUB_APP_ID!,
|
|
101
|
+
privateKey: process.env.GITHUB_APP_PRIVATE_KEY_BASE64!, // Base64 encoded
|
|
102
|
+
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
|
|
103
|
+
clientId: process.env.GITHUB_APP_CLIENT_ID!,
|
|
104
|
+
clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!,
|
|
105
|
+
|
|
106
|
+
// Optional: App slug for installation URL
|
|
107
|
+
appSlug: "my-flink-app",
|
|
108
|
+
|
|
109
|
+
// Required: Callback after successful installation
|
|
110
|
+
onInstallationSuccess: async ({ installationId, repositories, account }, ctx) => {
|
|
111
|
+
// Your app determines how to get userId
|
|
112
|
+
// This could come from session, custom auth, or any other source
|
|
113
|
+
const userId = getUserIdFromSession(); // Your implementation
|
|
114
|
+
|
|
115
|
+
console.log(`User ${userId} installed app on ${account.login}`);
|
|
116
|
+
console.log(`Granted access to ${repositories.length} repositories`);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
userId, // Link installation to this user
|
|
120
|
+
redirectUrl: "/dashboard/repos", // Where to redirect after installation
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// Optional: Handle webhook events
|
|
125
|
+
onWebhookEvent: async ({ event, action, payload, installationId }, ctx) => {
|
|
126
|
+
console.log(`Received ${event} event for installation ${installationId}`);
|
|
127
|
+
|
|
128
|
+
// Handle specific events
|
|
129
|
+
if (event === "push") {
|
|
130
|
+
console.log(`Push to ${payload.repository.full_name}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (event === "pull_request" && action === "opened") {
|
|
134
|
+
console.log(`New PR #${payload.pull_request.number}`);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Optional: Handle installation errors
|
|
139
|
+
onInstallationError: async ({ error, installationId }) => {
|
|
140
|
+
console.error(`Installation error:`, error);
|
|
141
|
+
return {
|
|
142
|
+
redirectUrl: "/error?message=installation-failed",
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
}),
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await app.start();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
### GitHubAppPluginOptions
|
|
155
|
+
|
|
156
|
+
| Option | Type | Required | Default | Description |
|
|
157
|
+
| ----------------------------- | ---------- | -------- | ------------------------ | ------------------------------------------------- |
|
|
158
|
+
| `appId` | `string` | Yes | - | GitHub App ID |
|
|
159
|
+
| `privateKey` | `string` | Yes | - | Base64 encoded RSA private key (PKCS#1 or PKCS#8) |
|
|
160
|
+
| `webhookSecret` | `string` | Yes | - | Webhook secret for signature validation |
|
|
161
|
+
| `clientId` | `string` | Yes | - | GitHub App client ID |
|
|
162
|
+
| `clientSecret` | `string` | Yes | - | GitHub App client secret |
|
|
163
|
+
| `appSlug` | `string` | No | Auto-detected | GitHub App slug (used in installation URL) |
|
|
164
|
+
| `baseUrl` | `string` | No | `https://api.github.com` | GitHub API base URL (for GitHub Enterprise) |
|
|
165
|
+
| `onInstallationSuccess` | `Function` | Yes | - | Callback after successful installation |
|
|
166
|
+
| `onInstallationError` | `Function` | No | - | Callback on installation errors |
|
|
167
|
+
| `onWebhookEvent` | `Function` | No | - | Callback for webhook events |
|
|
168
|
+
| `sessionsCollectionName` | `string` | No | `github_app_sessions` | MongoDB collection for sessions |
|
|
169
|
+
| `installationsCollectionName` | `string` | No | `github_installations` | MongoDB collection for installations |
|
|
170
|
+
| `webhookEventsCollectionName` | `string` | No | `github_webhook_events` | MongoDB collection for webhook events |
|
|
171
|
+
| `tokenCacheTTL` | `number` | No | `3300` (55 minutes) | Installation token cache TTL in seconds |
|
|
172
|
+
| `sessionTTL` | `number` | No | `600` (10 minutes) | Session TTL in seconds |
|
|
173
|
+
| `registerRoutes` | `boolean` | No | `true` | Register HTTP handlers automatically |
|
|
174
|
+
| `logWebhookEvents` | `boolean` | No | `false` | Log webhook events to MongoDB |
|
|
175
|
+
|
|
176
|
+
### Callback Functions
|
|
177
|
+
|
|
178
|
+
#### onInstallationSuccess (Required)
|
|
179
|
+
|
|
180
|
+
Called when a user successfully installs the GitHub App.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
onInstallationSuccess: async (
|
|
184
|
+
params: {
|
|
185
|
+
installationId: number;
|
|
186
|
+
account: {
|
|
187
|
+
id: number;
|
|
188
|
+
login: string;
|
|
189
|
+
type: "User" | "Organization";
|
|
190
|
+
avatarUrl?: string;
|
|
191
|
+
};
|
|
192
|
+
repositories: {
|
|
193
|
+
id: number;
|
|
194
|
+
name: string;
|
|
195
|
+
fullName: string;
|
|
196
|
+
private: boolean;
|
|
197
|
+
}[];
|
|
198
|
+
permissions: Record<string, string>;
|
|
199
|
+
events: string[];
|
|
200
|
+
},
|
|
201
|
+
ctx: Context
|
|
202
|
+
) =>
|
|
203
|
+
Promise<{
|
|
204
|
+
userId: string;
|
|
205
|
+
redirectUrl?: string;
|
|
206
|
+
}>;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Example:**
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
onInstallationSuccess: async ({ installationId, repositories, account }, ctx) => {
|
|
213
|
+
// Get userId from your authentication system
|
|
214
|
+
const userId = ctx.session?.userId || "anonymous";
|
|
215
|
+
|
|
216
|
+
// Optionally notify user
|
|
217
|
+
console.log(`Installation ${installationId} linked to user ${userId}`);
|
|
218
|
+
console.log(`Access granted to ${repositories.length} repositories`);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
userId, // Required: Link installation to this user
|
|
222
|
+
redirectUrl: "/dashboard", // Optional: Redirect after installation
|
|
223
|
+
};
|
|
224
|
+
};
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
#### onInstallationError (Optional)
|
|
228
|
+
|
|
229
|
+
Called when installation fails.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
onInstallationError: async (params: {
|
|
233
|
+
error: {
|
|
234
|
+
code: string;
|
|
235
|
+
message: string;
|
|
236
|
+
details?: any;
|
|
237
|
+
};
|
|
238
|
+
installationId?: number;
|
|
239
|
+
}) =>
|
|
240
|
+
Promise<{
|
|
241
|
+
redirectUrl?: string;
|
|
242
|
+
}>;
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### onWebhookEvent (Optional)
|
|
246
|
+
|
|
247
|
+
Called when a webhook event is received.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
onWebhookEvent: async (
|
|
251
|
+
params: {
|
|
252
|
+
event: string;
|
|
253
|
+
action?: string;
|
|
254
|
+
payload: Record<string, any>;
|
|
255
|
+
installationId: number;
|
|
256
|
+
deliveryId: string;
|
|
257
|
+
},
|
|
258
|
+
ctx: Context
|
|
259
|
+
) => Promise<void>;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
onWebhookEvent: async ({ event, action, payload, installationId }, ctx) => {
|
|
266
|
+
switch (event) {
|
|
267
|
+
case "push":
|
|
268
|
+
console.log(`Push to ${payload.repository.full_name}`);
|
|
269
|
+
break;
|
|
270
|
+
|
|
271
|
+
case "pull_request":
|
|
272
|
+
if (action === "opened") {
|
|
273
|
+
const client = await ctx.plugins.githubApp.getClient(installationId);
|
|
274
|
+
await client.createIssue(payload.repository.owner.login, payload.repository.name, {
|
|
275
|
+
title: "Thanks for the PR!",
|
|
276
|
+
body: "We appreciate your contribution.",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
|
|
281
|
+
case "installation":
|
|
282
|
+
if (action === "deleted") {
|
|
283
|
+
console.log(`Installation ${installationId} was deleted`);
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Installation Flow
|
|
291
|
+
|
|
292
|
+
### How Users Install Your GitHub App
|
|
293
|
+
|
|
294
|
+
1. User navigates to: `GET /github-app/install?user_id=USER_ID`
|
|
295
|
+
- The `user_id` query parameter is optional and determined by your app
|
|
296
|
+
2. User is redirected to GitHub's installation page
|
|
297
|
+
3. User selects repositories to grant access
|
|
298
|
+
4. User clicks "Install" or "Install & Authorize"
|
|
299
|
+
5. GitHub redirects back to: `GET /github-app/callback?installation_id=...&state=...`
|
|
300
|
+
6. Plugin validates the state parameter (CSRF protection)
|
|
301
|
+
7. Plugin fetches installation details from GitHub
|
|
302
|
+
8. Plugin calls your `onInstallationSuccess` callback
|
|
303
|
+
9. Plugin stores installation in MongoDB
|
|
304
|
+
10. User is redirected to your app
|
|
305
|
+
|
|
306
|
+
### Initiating Installation from Your App
|
|
307
|
+
|
|
308
|
+
**HTML Button:**
|
|
309
|
+
|
|
310
|
+
```html
|
|
311
|
+
<a href="/github-app/install?user_id=123">Install GitHub App</a>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**React Component:**
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
function InstallGitHubApp() {
|
|
318
|
+
const handleInstall = () => {
|
|
319
|
+
const userId = getCurrentUserId(); // Your function
|
|
320
|
+
window.location.href = `/github-app/install?user_id=${userId}`;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return <button onClick={handleInstall}>Connect GitHub</button>;
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Webhook Setup
|
|
328
|
+
|
|
329
|
+
### 1. Configure Webhook in GitHub App Settings
|
|
330
|
+
|
|
331
|
+
- **Webhook URL:** `https://yourdomain.com/github-app/webhook`
|
|
332
|
+
- **Webhook Secret:** Same secret used in plugin configuration
|
|
333
|
+
- **Events:** Select events you want to receive (push, PR, issues, etc.)
|
|
334
|
+
|
|
335
|
+
### 2. Handle Webhook Events
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
onWebhookEvent: async ({ event, action, payload, installationId, deliveryId }, ctx) => {
|
|
339
|
+
console.log(`Event: ${event}, Action: ${action}, Delivery: ${deliveryId}`);
|
|
340
|
+
|
|
341
|
+
// Access installation
|
|
342
|
+
const installation = await ctx.repos.githubInstallationRepo.findByInstallationId(installationId);
|
|
343
|
+
|
|
344
|
+
// Get API client
|
|
345
|
+
const client = await ctx.plugins.githubApp.getClient(installationId);
|
|
346
|
+
|
|
347
|
+
// Process event
|
|
348
|
+
if (event === "push") {
|
|
349
|
+
const commits = payload.commits;
|
|
350
|
+
console.log(`Received ${commits.length} commits`);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### 3. Webhook Signature Validation
|
|
356
|
+
|
|
357
|
+
The plugin automatically validates webhook signatures using HMAC-SHA256 with constant-time comparison. Invalid signatures are rejected with a 401 status code.
|
|
358
|
+
|
|
359
|
+
## Context API
|
|
360
|
+
|
|
361
|
+
The plugin exposes methods via `ctx.plugins.githubApp`:
|
|
362
|
+
|
|
363
|
+
### getClient(installationId)
|
|
364
|
+
|
|
365
|
+
Get GitHub API client for an installation.
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const client = await ctx.plugins.githubApp.getClient(12345);
|
|
369
|
+
const repos = await client.getRepositories();
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### getInstallation(userId)
|
|
373
|
+
|
|
374
|
+
Get installation for a user (returns first installation if multiple exist).
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
const installation = await ctx.plugins.githubApp.getInstallation("user-123");
|
|
378
|
+
if (installation) {
|
|
379
|
+
console.log(`Installed on: ${installation.accountLogin}`);
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### getInstallations(userId)
|
|
384
|
+
|
|
385
|
+
Get all installations for a user.
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
const installations = await ctx.plugins.githubApp.getInstallations("user-123");
|
|
389
|
+
installations.forEach((inst) => {
|
|
390
|
+
console.log(`${inst.accountLogin} (${inst.accountType})`);
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### deleteInstallation(userId, installationId)
|
|
395
|
+
|
|
396
|
+
Delete an installation from the database.
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
await ctx.plugins.githubApp.deleteInstallation("user-123", 12345);
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### hasRepositoryAccess(userId, owner, repo)
|
|
403
|
+
|
|
404
|
+
Check if user has access to a specific repository.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const hasAccess = await ctx.plugins.githubApp.hasRepositoryAccess("user-123", "facebook", "react");
|
|
408
|
+
|
|
409
|
+
if (!hasAccess) {
|
|
410
|
+
return forbidden("You do not have access to this repository");
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### getInstallationToken(installationId)
|
|
415
|
+
|
|
416
|
+
Get raw installation access token (for advanced usage).
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const token = await ctx.plugins.githubApp.getInstallationToken(12345);
|
|
420
|
+
// Make custom API call with token
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### clearTokenCache()
|
|
424
|
+
|
|
425
|
+
Clear all cached installation tokens.
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
ctx.plugins.githubApp.clearTokenCache();
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## GitHub API Client
|
|
432
|
+
|
|
433
|
+
The plugin provides a GitHub API client with automatic token injection:
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
const client = await ctx.plugins.githubApp.getClient(installationId);
|
|
437
|
+
|
|
438
|
+
// Get repositories accessible by this installation
|
|
439
|
+
const repos = await client.getRepositories();
|
|
440
|
+
|
|
441
|
+
// Get specific repository
|
|
442
|
+
const repo = await client.getRepository("facebook", "react");
|
|
443
|
+
|
|
444
|
+
// Get file contents
|
|
445
|
+
const contents = await client.getContents("facebook", "react", "README.md");
|
|
446
|
+
|
|
447
|
+
// Create an issue
|
|
448
|
+
const issue = await client.createIssue("facebook", "react", {
|
|
449
|
+
title: "Bug Report",
|
|
450
|
+
body: "Found a bug...",
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Generic API call
|
|
454
|
+
const response = await client.request("GET", "/rate_limit");
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Standalone Usage (No JWT Auth Plugin)
|
|
458
|
+
|
|
459
|
+
This plugin is **standalone** and does NOT require the JWT Auth Plugin. It works with any authentication system.
|
|
460
|
+
|
|
461
|
+
### Example with Custom Session-Based Auth
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
githubAppPlugin({
|
|
465
|
+
// ... config
|
|
466
|
+
|
|
467
|
+
onInstallationSuccess: async ({ installationId, repositories }, ctx) => {
|
|
468
|
+
// Get userId from your custom session system
|
|
469
|
+
const userId = ctx.req.session?.userId;
|
|
470
|
+
|
|
471
|
+
if (!userId) {
|
|
472
|
+
throw new Error("User not authenticated");
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
userId,
|
|
477
|
+
redirectUrl: "/dashboard",
|
|
478
|
+
};
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Example with Custom Token-Based Auth
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
githubAppPlugin({
|
|
487
|
+
// ... config
|
|
488
|
+
|
|
489
|
+
onInstallationSuccess: async ({ installationId, repositories }, ctx) => {
|
|
490
|
+
// Get userId from your custom auth token
|
|
491
|
+
const authHeader = ctx.req.headers.authorization;
|
|
492
|
+
const userId = parseAuthToken(authHeader); // Your function
|
|
493
|
+
|
|
494
|
+
return {
|
|
495
|
+
userId,
|
|
496
|
+
redirectUrl: "/dashboard",
|
|
497
|
+
};
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Integration with JWT Auth Plugin (Optional)
|
|
503
|
+
|
|
504
|
+
While the plugin works standalone, you can optionally use it with the JWT Auth Plugin:
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
|
|
508
|
+
|
|
509
|
+
const app = new FlinkApp<Context>({
|
|
510
|
+
auth: jwtAuthPlugin({
|
|
511
|
+
secret: process.env.JWT_SECRET!,
|
|
512
|
+
// ... JWT config
|
|
513
|
+
}),
|
|
514
|
+
|
|
515
|
+
plugins: [
|
|
516
|
+
githubAppPlugin({
|
|
517
|
+
// ... config
|
|
518
|
+
|
|
519
|
+
onInstallationSuccess: async ({ installationId, repositories }, ctx) => {
|
|
520
|
+
// Access JWT Auth Plugin context (if available)
|
|
521
|
+
const userId = ctx.auth?.tokenData?.userId || "anonymous";
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
userId,
|
|
525
|
+
redirectUrl: "/dashboard",
|
|
526
|
+
};
|
|
527
|
+
},
|
|
528
|
+
}),
|
|
529
|
+
],
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Security Considerations
|
|
534
|
+
|
|
535
|
+
### Private Key Management
|
|
536
|
+
|
|
537
|
+
- Store base64 encoded private key in environment variables
|
|
538
|
+
- Never commit private key to version control
|
|
539
|
+
- Encode keys using base64 before storing in environment variables
|
|
540
|
+
- Original key must be in PEM format (PKCS#1 or PKCS#8)
|
|
541
|
+
- Rotate keys periodically
|
|
542
|
+
|
|
543
|
+
### JWT Signing Security
|
|
544
|
+
|
|
545
|
+
- Uses RS256 algorithm with RSA private key
|
|
546
|
+
- Tokens expire after 10 minutes
|
|
547
|
+
- Automatic key format detection (PKCS#1 and PKCS#8)
|
|
548
|
+
|
|
549
|
+
### Webhook Signature Validation
|
|
550
|
+
|
|
551
|
+
- HMAC-SHA256 signature validation
|
|
552
|
+
- Constant-time comparison to prevent timing attacks
|
|
553
|
+
- Rejects webhooks with invalid signatures
|
|
554
|
+
|
|
555
|
+
### CSRF Protection
|
|
556
|
+
|
|
557
|
+
- State parameter with cryptographically secure random generation
|
|
558
|
+
- Session stored with TTL (default: 10 minutes)
|
|
559
|
+
- One-time use: session deleted after successful callback
|
|
560
|
+
- Constant-time comparison for state validation
|
|
561
|
+
|
|
562
|
+
### Token Caching Security
|
|
563
|
+
|
|
564
|
+
- Tokens cached in memory only (never in database)
|
|
565
|
+
- Automatic expiration after 55 minutes (tokens expire at 60 minutes)
|
|
566
|
+
- Clear cache on demand via `clearTokenCache()`
|
|
567
|
+
|
|
568
|
+
### HTTPS Requirements
|
|
569
|
+
|
|
570
|
+
All GitHub API calls and webhook URLs must use HTTPS in production.
|
|
571
|
+
|
|
572
|
+
## Troubleshooting
|
|
573
|
+
|
|
574
|
+
### Invalid Private Key Format
|
|
575
|
+
|
|
576
|
+
**Issue:** `invalid-private-key` error on plugin initialization
|
|
577
|
+
|
|
578
|
+
**Solution:**
|
|
579
|
+
|
|
580
|
+
- Ensure private key is base64 encoded before storing in environment variable
|
|
581
|
+
- Verify original PEM key starts with `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1) or `-----BEGIN PRIVATE KEY-----` (PKCS#8)
|
|
582
|
+
- Use the encoding commands: `base64 -i private-key.pem | tr -d '\n'` (macOS/Linux)
|
|
583
|
+
- Ensure entire base64 string is included in environment variable with no line breaks
|
|
584
|
+
|
|
585
|
+
### Webhook Signature Validation Failed
|
|
586
|
+
|
|
587
|
+
**Issue:** Webhooks rejected with 401 status
|
|
588
|
+
|
|
589
|
+
**Solution:**
|
|
590
|
+
|
|
591
|
+
- Verify webhook secret matches exactly
|
|
592
|
+
- Check webhook secret is set in GitHub App settings
|
|
593
|
+
- Ensure raw request body is used (not parsed JSON)
|
|
594
|
+
|
|
595
|
+
### Installation State Mismatch
|
|
596
|
+
|
|
597
|
+
**Issue:** `invalid-state` error during callback
|
|
598
|
+
|
|
599
|
+
**Solution:**
|
|
600
|
+
|
|
601
|
+
- Ensure MongoDB is running and accessible
|
|
602
|
+
- Check session TTL hasn't expired (default: 10 minutes)
|
|
603
|
+
- Verify cookies are enabled
|
|
604
|
+
- Check clock synchronization between servers
|
|
605
|
+
|
|
606
|
+
### Token Cache Not Working
|
|
607
|
+
|
|
608
|
+
**Issue:** Too many GitHub API calls
|
|
609
|
+
|
|
610
|
+
**Solution:**
|
|
611
|
+
|
|
612
|
+
- Verify `tokenCacheTTL` is set appropriately (default: 55 minutes)
|
|
613
|
+
- Check memory usage (tokens cached in-memory)
|
|
614
|
+
- Call `clearTokenCache()` only when necessary
|
|
615
|
+
|
|
616
|
+
### Installation Not Found
|
|
617
|
+
|
|
618
|
+
**Issue:** `installation-not-found` error
|
|
619
|
+
|
|
620
|
+
**Solution:**
|
|
621
|
+
|
|
622
|
+
- Verify user has installed the GitHub App
|
|
623
|
+
- Check MongoDB for installation record
|
|
624
|
+
- Ensure `userId` matches the one stored during installation
|
|
625
|
+
|
|
626
|
+
## API Reference
|
|
627
|
+
|
|
628
|
+
See TypeScript interfaces for complete type definitions:
|
|
629
|
+
|
|
630
|
+
- `GitHubAppPluginOptions` - Plugin configuration
|
|
631
|
+
- `GitHubAppPluginContext` - Context API methods
|
|
632
|
+
- `GitHubInstallation` - Installation model
|
|
633
|
+
- `WebhookEvent` - Webhook event model
|
|
634
|
+
- `GitHubAPIClient` - API client methods
|
|
635
|
+
|
|
636
|
+
## Examples
|
|
637
|
+
|
|
638
|
+
See the `examples/` directory for complete working examples:
|
|
639
|
+
|
|
640
|
+
- `basic-installation.ts` - Basic GitHub App installation
|
|
641
|
+
- `webhook-handling.ts` - Process webhook events
|
|
642
|
+
- `repository-access.ts` - Access repositories via API client
|
|
643
|
+
- `create-issue.ts` - Create GitHub issue with permission check
|
|
644
|
+
- `with-jwt-auth.ts` - Optional integration with JWT Auth Plugin
|
|
645
|
+
- `organization-installation.ts` - Organization-level installation
|
|
646
|
+
- `error-handling.ts` - Comprehensive error handling
|
|
647
|
+
- `multi-event-webhook.ts` - Handle multiple webhook event types
|
|
648
|
+
|
|
649
|
+
## Production Checklist
|
|
650
|
+
|
|
651
|
+
- [ ] GitHub App created with proper permissions
|
|
652
|
+
- [ ] Webhook URL configured with HTTPS
|
|
653
|
+
- [ ] Private key stored securely in environment variables
|
|
654
|
+
- [ ] Webhook secret configured and stored securely
|
|
655
|
+
- [ ] MongoDB connection configured and tested
|
|
656
|
+
- [ ] `onInstallationSuccess` callback implemented
|
|
657
|
+
- [ ] Webhook event handling implemented
|
|
658
|
+
- [ ] Error handling configured
|
|
659
|
+
- [ ] HTTPS enabled for all endpoints
|
|
660
|
+
- [ ] Rate limiting configured (app-level)
|
|
661
|
+
- [ ] Monitoring and logging set up
|
|
662
|
+
- [ ] Test installation flow end-to-end
|
|
663
|
+
- [ ] Test webhook delivery and signature validation
|
|
664
|
+
|
|
665
|
+
## License
|
|
666
|
+
|
|
667
|
+
MIT
|