@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.
Files changed (96) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/LICENSE +21 -0
  3. package/README.md +667 -0
  4. package/SECURITY.md +498 -0
  5. package/dist/GitHubAppInternalContext.d.ts +44 -0
  6. package/dist/GitHubAppInternalContext.js +2 -0
  7. package/dist/GitHubAppPlugin.d.ts +45 -0
  8. package/dist/GitHubAppPlugin.js +367 -0
  9. package/dist/GitHubAppPluginContext.d.ts +242 -0
  10. package/dist/GitHubAppPluginContext.js +2 -0
  11. package/dist/GitHubAppPluginOptions.d.ts +369 -0
  12. package/dist/GitHubAppPluginOptions.js +2 -0
  13. package/dist/handlers/InitiateInstallation.d.ts +32 -0
  14. package/dist/handlers/InitiateInstallation.js +66 -0
  15. package/dist/handlers/InstallationCallback.d.ts +42 -0
  16. package/dist/handlers/InstallationCallback.js +248 -0
  17. package/dist/handlers/UninstallHandler.d.ts +37 -0
  18. package/dist/handlers/UninstallHandler.js +153 -0
  19. package/dist/handlers/WebhookHandler.d.ts +54 -0
  20. package/dist/handlers/WebhookHandler.js +157 -0
  21. package/dist/index.d.ts +19 -0
  22. package/dist/index.js +23 -0
  23. package/dist/repos/GitHubAppSessionRepo.d.ts +24 -0
  24. package/dist/repos/GitHubAppSessionRepo.js +32 -0
  25. package/dist/repos/GitHubInstallationRepo.d.ts +53 -0
  26. package/dist/repos/GitHubInstallationRepo.js +83 -0
  27. package/dist/repos/GitHubWebhookEventRepo.d.ts +29 -0
  28. package/dist/repos/GitHubWebhookEventRepo.js +42 -0
  29. package/dist/schemas/GitHubAppSession.d.ts +13 -0
  30. package/dist/schemas/GitHubAppSession.js +2 -0
  31. package/dist/schemas/GitHubInstallation.d.ts +28 -0
  32. package/dist/schemas/GitHubInstallation.js +2 -0
  33. package/dist/schemas/InstallationCallbackRequest.d.ts +10 -0
  34. package/dist/schemas/InstallationCallbackRequest.js +2 -0
  35. package/dist/schemas/WebhookEvent.d.ts +16 -0
  36. package/dist/schemas/WebhookEvent.js +2 -0
  37. package/dist/schemas/WebhookPayload.d.ts +35 -0
  38. package/dist/schemas/WebhookPayload.js +2 -0
  39. package/dist/services/GitHubAPIClient.d.ts +143 -0
  40. package/dist/services/GitHubAPIClient.js +167 -0
  41. package/dist/services/GitHubAuthService.d.ts +85 -0
  42. package/dist/services/GitHubAuthService.js +160 -0
  43. package/dist/services/WebhookValidator.d.ts +93 -0
  44. package/dist/services/WebhookValidator.js +123 -0
  45. package/dist/utils/error-utils.d.ts +67 -0
  46. package/dist/utils/error-utils.js +121 -0
  47. package/dist/utils/jwt-utils.d.ts +35 -0
  48. package/dist/utils/jwt-utils.js +67 -0
  49. package/dist/utils/state-utils.d.ts +38 -0
  50. package/dist/utils/state-utils.js +74 -0
  51. package/dist/utils/token-cache-utils.d.ts +47 -0
  52. package/dist/utils/token-cache-utils.js +74 -0
  53. package/dist/utils/webhook-signature-utils.d.ts +22 -0
  54. package/dist/utils/webhook-signature-utils.js +57 -0
  55. package/examples/basic-installation.ts +246 -0
  56. package/examples/create-issue.ts +392 -0
  57. package/examples/error-handling.ts +396 -0
  58. package/examples/multi-event-webhook.ts +367 -0
  59. package/examples/organization-installation.ts +316 -0
  60. package/examples/repository-access.ts +480 -0
  61. package/examples/webhook-handling.ts +343 -0
  62. package/examples/with-jwt-auth.ts +319 -0
  63. package/package.json +41 -0
  64. package/spec/core-utilities.spec.ts +243 -0
  65. package/spec/handlers.spec.ts +216 -0
  66. package/spec/helpers/reporter.ts +41 -0
  67. package/spec/integration-and-security.spec.ts +483 -0
  68. package/spec/plugin-core.spec.ts +258 -0
  69. package/spec/project-setup.spec.ts +56 -0
  70. package/spec/repos-and-schemas.spec.ts +288 -0
  71. package/spec/services.spec.ts +108 -0
  72. package/spec/support/jasmine.json +7 -0
  73. package/src/GitHubAppPlugin.ts +411 -0
  74. package/src/GitHubAppPluginContext.ts +254 -0
  75. package/src/GitHubAppPluginOptions.ts +412 -0
  76. package/src/handlers/InstallationCallback.ts +292 -0
  77. package/src/handlers/WebhookHandler.ts +179 -0
  78. package/src/index.ts +29 -0
  79. package/src/repos/GitHubAppSessionRepo.ts +36 -0
  80. package/src/repos/GitHubInstallationRepo.ts +95 -0
  81. package/src/repos/GitHubWebhookEventRepo.ts +48 -0
  82. package/src/schemas/GitHubAppSession.ts +13 -0
  83. package/src/schemas/GitHubInstallation.ts +28 -0
  84. package/src/schemas/InstallationCallbackRequest.ts +10 -0
  85. package/src/schemas/WebhookEvent.ts +16 -0
  86. package/src/schemas/WebhookPayload.ts +35 -0
  87. package/src/services/GitHubAPIClient.ts +244 -0
  88. package/src/services/GitHubAuthService.ts +188 -0
  89. package/src/services/WebhookValidator.ts +159 -0
  90. package/src/utils/error-utils.ts +148 -0
  91. package/src/utils/jwt-utils.ts +64 -0
  92. package/src/utils/state-utils.ts +72 -0
  93. package/src/utils/token-cache-utils.ts +89 -0
  94. package/src/utils/webhook-signature-utils.ts +57 -0
  95. package/tsconfig.dist.json +4 -0
  96. 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