@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
@@ -0,0 +1,343 @@
1
+ /**
2
+ * Webhook Handling Example
3
+ *
4
+ * This example demonstrates:
5
+ * - Processing webhook events (push, pull request, installation)
6
+ * - Webhook signature validation (automatic)
7
+ * - Accessing webhook payload data
8
+ * - Using GitHub API client in webhook handlers
9
+ * - Responding to different event types
10
+ */
11
+
12
+ import { FlinkApp, FlinkContext } from "@flink-app/flink";
13
+ import { githubAppPlugin } from "@flink-app/github-app-plugin";
14
+
15
+ interface AppContext extends FlinkContext {
16
+ plugins: {
17
+ githubApp: any;
18
+ };
19
+ }
20
+
21
+ async function start() {
22
+ const app = new FlinkApp<AppContext>({
23
+ name: "GitHub App Webhook Example",
24
+ port: 3333,
25
+
26
+ db: {
27
+ uri: process.env.MONGODB_URI || "mongodb://localhost:27017/github-app-webhook-example",
28
+ },
29
+
30
+ plugins: [
31
+ githubAppPlugin({
32
+ appId: process.env.GITHUB_APP_ID!,
33
+ privateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
34
+ webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
35
+ clientId: process.env.GITHUB_APP_CLIENT_ID!,
36
+ clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!,
37
+
38
+ onInstallationSuccess: async ({ installationId, account }) => {
39
+ console.log(`Installation ${installationId} created for ${account.login}`);
40
+ return {
41
+ userId: "demo-user",
42
+ redirectUrl: "/dashboard",
43
+ };
44
+ },
45
+
46
+ // Webhook event handler
47
+ onWebhookEvent: async ({ event, action, payload, installationId, deliveryId }, ctx) => {
48
+ console.log(`\n=== Webhook Received ===`);
49
+ console.log(`Event: ${event}`);
50
+ console.log(`Action: ${action || "N/A"}`);
51
+ console.log(`Installation ID: ${installationId}`);
52
+ console.log(`Delivery ID: ${deliveryId}`);
53
+
54
+ try {
55
+ // Get API client for this installation
56
+ const client = await ctx.plugins.githubApp.getClient(installationId);
57
+
58
+ // Handle different event types
59
+ switch (event) {
60
+ case "push":
61
+ await handlePushEvent(payload, client);
62
+ break;
63
+
64
+ case "pull_request":
65
+ await handlePullRequestEvent(payload, action, client);
66
+ break;
67
+
68
+ case "issues":
69
+ await handleIssuesEvent(payload, action, client);
70
+ break;
71
+
72
+ case "installation":
73
+ await handleInstallationEvent(payload, action, ctx);
74
+ break;
75
+
76
+ case "installation_repositories":
77
+ await handleRepositoriesEvent(payload, action, ctx);
78
+ break;
79
+
80
+ case "pull_request_review":
81
+ await handlePullRequestReviewEvent(payload, action, client);
82
+ break;
83
+
84
+ case "issue_comment":
85
+ await handleIssueCommentEvent(payload, action, client);
86
+ break;
87
+
88
+ default:
89
+ console.log(`Unhandled event type: ${event}`);
90
+ }
91
+
92
+ console.log(`Webhook processed successfully`);
93
+ } catch (error) {
94
+ console.error(`Error processing webhook:`, error);
95
+ // Don't throw - return 200 to GitHub to prevent retries
96
+ }
97
+ },
98
+
99
+ // Optional: Log webhook events to MongoDB for debugging
100
+ logWebhookEvents: true,
101
+ }),
102
+ ],
103
+ });
104
+
105
+ await app.start();
106
+
107
+ console.log(`
108
+ =================================
109
+ Webhook Handling Example Started
110
+ =================================
111
+
112
+ Webhook endpoint: http://localhost:3333/github-app/webhook
113
+
114
+ For local development, use ngrok to create HTTPS tunnel:
115
+ ngrok http 3333
116
+
117
+ Then configure webhook URL in GitHub App settings:
118
+ https://your-ngrok-url.ngrok.io/github-app/webhook
119
+
120
+ Events will be logged to console when received.
121
+
122
+ =================================
123
+ `);
124
+ }
125
+
126
+ // Handle push events
127
+ async function handlePushEvent(payload: any, client: any) {
128
+ const repo = payload.repository;
129
+ const pusher = payload.pusher;
130
+ const commits = payload.commits || [];
131
+ const branch = payload.ref.replace("refs/heads/", "");
132
+
133
+ console.log(`\n📦 Push Event:`);
134
+ console.log(` Repository: ${repo.full_name}`);
135
+ console.log(` Branch: ${branch}`);
136
+ console.log(` Pusher: ${pusher.name}`);
137
+ console.log(` Commits: ${commits.length}`);
138
+
139
+ // Log commit messages
140
+ commits.forEach((commit: any) => {
141
+ console.log(` - ${commit.message.split("\n")[0]} (${commit.id.substring(0, 7)})`);
142
+ });
143
+
144
+ // Example: Create issue if commit message contains "[bug]"
145
+ const hasBugCommit = commits.some((commit: any) => commit.message.toLowerCase().includes("[bug]"));
146
+
147
+ if (hasBugCommit) {
148
+ console.log(` 🐛 Bug commit detected, creating issue...`);
149
+
150
+ try {
151
+ const issue = await client.createIssue(repo.owner.login, repo.name, {
152
+ title: `Bug fix needed in ${branch}`,
153
+ body: `A commit with [bug] tag was pushed to ${branch}.\n\nCommits:\n${commits
154
+ .map((c: any) => `- ${c.message.split("\n")[0]}`)
155
+ .join("\n")}`,
156
+ });
157
+
158
+ console.log(` ✅ Issue created: #${issue.number}`);
159
+ } catch (error) {
160
+ console.error(` ❌ Failed to create issue:`, error);
161
+ }
162
+ }
163
+ }
164
+
165
+ // Handle pull request events
166
+ async function handlePullRequestEvent(payload: any, action: string | undefined, client: any) {
167
+ const pr = payload.pull_request;
168
+ const repo = payload.repository;
169
+
170
+ console.log(`\n🔀 Pull Request Event:`);
171
+ console.log(` Action: ${action}`);
172
+ console.log(` Repository: ${repo.full_name}`);
173
+ console.log(` PR: #${pr.number} - ${pr.title}`);
174
+ console.log(` Author: ${pr.user.login}`);
175
+ console.log(` Base: ${pr.base.ref} <- Head: ${pr.head.ref}`);
176
+
177
+ if (action === "opened") {
178
+ // Thank contributor for opening PR
179
+ console.log(` 💬 Welcoming new contributor...`);
180
+
181
+ try {
182
+ await client.request("POST", `/repos/${repo.owner.login}/${repo.name}/issues/${pr.number}/comments`, {
183
+ body: `Thanks for opening this pull request, @${pr.user.login}! We'll review it soon.`,
184
+ });
185
+
186
+ console.log(` ✅ Comment added to PR`);
187
+ } catch (error) {
188
+ console.error(` ❌ Failed to add comment:`, error);
189
+ }
190
+ }
191
+
192
+ if (action === "closed" && pr.merged) {
193
+ console.log(` ✅ PR merged!`);
194
+ }
195
+ }
196
+
197
+ // Handle issues events
198
+ async function handleIssuesEvent(payload: any, action: string | undefined, client: any) {
199
+ const issue = payload.issue;
200
+ const repo = payload.repository;
201
+
202
+ console.log(`\n🐛 Issue Event:`);
203
+ console.log(` Action: ${action}`);
204
+ console.log(` Repository: ${repo.full_name}`);
205
+ console.log(` Issue: #${issue.number} - ${issue.title}`);
206
+ console.log(` Author: ${issue.user.login}`);
207
+
208
+ if (action === "opened") {
209
+ // Auto-label issues based on content
210
+ const labels: string[] = [];
211
+
212
+ if (issue.title.toLowerCase().includes("bug")) {
213
+ labels.push("bug");
214
+ }
215
+
216
+ if (issue.title.toLowerCase().includes("feature")) {
217
+ labels.push("enhancement");
218
+ }
219
+
220
+ if (labels.length > 0) {
221
+ console.log(` 🏷️ Auto-labeling issue with: ${labels.join(", ")}`);
222
+
223
+ try {
224
+ await client.request("POST", `/repos/${repo.owner.login}/${repo.name}/issues/${issue.number}/labels`, {
225
+ labels,
226
+ });
227
+
228
+ console.log(` ✅ Labels added`);
229
+ } catch (error) {
230
+ console.error(` ❌ Failed to add labels:`, error);
231
+ }
232
+ }
233
+ }
234
+ }
235
+
236
+ // Handle installation events
237
+ async function handleInstallationEvent(payload: any, action: string | undefined, ctx: any) {
238
+ const installation = payload.installation;
239
+ const account = installation.account;
240
+
241
+ console.log(`\n⚙️ Installation Event:`);
242
+ console.log(` Action: ${action}`);
243
+ console.log(` Installation ID: ${installation.id}`);
244
+ console.log(` Account: ${account.login} (${account.type})`);
245
+
246
+ if (action === "created") {
247
+ console.log(` ✅ New installation created`);
248
+ }
249
+
250
+ if (action === "deleted") {
251
+ console.log(` ❌ Installation deleted`);
252
+
253
+ // Clean up installation data
254
+ const installations = await ctx.repos.githubInstallationRepo.findByInstallationId(installation.id);
255
+
256
+ if (installations.length > 0) {
257
+ const inst = installations[0];
258
+ console.log(` 🗑️ Cleaning up installation data for user ${inst.userId}`);
259
+
260
+ try {
261
+ await ctx.plugins.githubApp.deleteInstallation(inst.userId, installation.id);
262
+ console.log(` ✅ Installation data cleaned up`);
263
+ } catch (error) {
264
+ console.error(` ❌ Failed to clean up:`, error);
265
+ }
266
+ }
267
+ }
268
+
269
+ if (action === "suspend") {
270
+ console.log(` ⏸️ Installation suspended`);
271
+ }
272
+
273
+ if (action === "unsuspend") {
274
+ console.log(` ▶️ Installation unsuspended`);
275
+ }
276
+ }
277
+
278
+ // Handle installation repositories events
279
+ async function handleRepositoriesEvent(payload: any, action: string | undefined, ctx: any) {
280
+ const installation = payload.installation;
281
+ const addedRepos = payload.repositories_added || [];
282
+ const removedRepos = payload.repositories_removed || [];
283
+
284
+ console.log(`\n📚 Installation Repositories Event:`);
285
+ console.log(` Action: ${action}`);
286
+ console.log(` Installation ID: ${installation.id}`);
287
+
288
+ if (addedRepos.length > 0) {
289
+ console.log(` ➕ Added repositories:`);
290
+ addedRepos.forEach((repo: any) => {
291
+ console.log(` - ${repo.full_name}`);
292
+ });
293
+ }
294
+
295
+ if (removedRepos.length > 0) {
296
+ console.log(` ➖ Removed repositories:`);
297
+ removedRepos.forEach((repo: any) => {
298
+ console.log(` - ${repo.full_name}`);
299
+ });
300
+ }
301
+ }
302
+
303
+ // Handle pull request review events
304
+ async function handlePullRequestReviewEvent(payload: any, action: string | undefined, client: any) {
305
+ const review = payload.review;
306
+ const pr = payload.pull_request;
307
+ const repo = payload.repository;
308
+
309
+ console.log(`\n👀 Pull Request Review Event:`);
310
+ console.log(` Action: ${action}`);
311
+ console.log(` Repository: ${repo.full_name}`);
312
+ console.log(` PR: #${pr.number}`);
313
+ console.log(` Reviewer: ${review.user.login}`);
314
+ console.log(` State: ${review.state}`);
315
+
316
+ if (review.state === "approved") {
317
+ console.log(` ✅ PR approved!`);
318
+ }
319
+
320
+ if (review.state === "changes_requested") {
321
+ console.log(` 📝 Changes requested`);
322
+ }
323
+ }
324
+
325
+ // Handle issue comment events
326
+ async function handleIssueCommentEvent(payload: any, action: string | undefined, client: any) {
327
+ const comment = payload.comment;
328
+ const issue = payload.issue;
329
+ const repo = payload.repository;
330
+
331
+ console.log(`\n💬 Issue Comment Event:`);
332
+ console.log(` Action: ${action}`);
333
+ console.log(` Repository: ${repo.full_name}`);
334
+ console.log(` Issue: #${issue.number}`);
335
+ console.log(` Commenter: ${comment.user.login}`);
336
+ console.log(` Comment: ${comment.body.substring(0, 100)}...`);
337
+ }
338
+
339
+ // Handle errors
340
+ start().catch((error) => {
341
+ console.error("Failed to start application:", error);
342
+ process.exit(1);
343
+ });
@@ -0,0 +1,319 @@
1
+ /**
2
+ * GitHub App with JWT Auth Plugin Integration (Optional)
3
+ *
4
+ * This example demonstrates:
5
+ * - Optional integration with JWT Auth Plugin
6
+ * - Using JWT-based authentication with GitHub App
7
+ * - Protecting endpoints with JWT authentication
8
+ * - Accessing GitHub App context with authenticated user
9
+ *
10
+ * Note: This integration is OPTIONAL. The GitHub App Plugin works standalone
11
+ * without JWT Auth Plugin.
12
+ */
13
+
14
+ import { FlinkApp, FlinkContext, GetHandler, FlinkRepo } from "@flink-app/flink";
15
+ import { githubAppPlugin } from "@flink-app/github-app-plugin";
16
+ // Note: jwtAuthPlugin is optional - install separately if needed
17
+ // import { jwtAuthPlugin } from "@flink-app/jwt-auth-plugin";
18
+
19
+ interface User {
20
+ _id?: string;
21
+ email: string;
22
+ name: string;
23
+ roles: string[];
24
+ githubInstallations: number[];
25
+ createdAt: Date;
26
+ }
27
+
28
+ class UserRepo extends FlinkRepo<AppContext, User> {
29
+ async findByEmail(email: string) {
30
+ return this.getOne({ email });
31
+ }
32
+ }
33
+
34
+ interface AppContext extends FlinkContext {
35
+ repos: {
36
+ userRepo: UserRepo;
37
+ };
38
+ plugins: {
39
+ githubApp: any;
40
+ // jwtAuth would be available if JWT Auth Plugin is installed
41
+ jwtAuth?: any;
42
+ };
43
+ // JWT Auth Plugin adds this to context
44
+ auth?: {
45
+ tokenData?: {
46
+ userId: string;
47
+ email: string;
48
+ roles: string[];
49
+ };
50
+ user?: User;
51
+ };
52
+ }
53
+
54
+ async function start() {
55
+ const app = new FlinkApp<AppContext>({
56
+ name: "GitHub App with JWT Auth Example",
57
+ port: 3333,
58
+
59
+ db: {
60
+ uri: process.env.MONGODB_URI || "mongodb://localhost:27017/github-app-jwt-example",
61
+ },
62
+
63
+ // OPTIONAL: Configure JWT Auth Plugin
64
+ // Uncomment if you want to use JWT authentication
65
+ /*
66
+ auth: jwtAuthPlugin({
67
+ secret: process.env.JWT_SECRET || "your-jwt-secret",
68
+
69
+ getUser: async (tokenData) => {
70
+ const user = await app.ctx.repos.userRepo.getById(tokenData.userId);
71
+ if (!user) {
72
+ throw new Error("User not found");
73
+ }
74
+ return user;
75
+ },
76
+
77
+ rolePermissions: {
78
+ user: ["read:own", "write:own"],
79
+ admin: ["read:all", "write:all"],
80
+ },
81
+
82
+ expiresIn: "7d",
83
+ }),
84
+ */
85
+
86
+ plugins: [
87
+ githubAppPlugin({
88
+ appId: process.env.GITHUB_APP_ID!,
89
+ privateKey: process.env.GITHUB_APP_PRIVATE_KEY!,
90
+ webhookSecret: process.env.GITHUB_WEBHOOK_SECRET!,
91
+ clientId: process.env.GITHUB_APP_CLIENT_ID!,
92
+ clientSecret: process.env.GITHUB_APP_CLIENT_SECRET!,
93
+
94
+ onInstallationSuccess: async ({ installationId, repositories, account }, ctx) => {
95
+ // If JWT Auth Plugin is available, get userId from JWT token
96
+ // Otherwise, get userId from your custom auth system
97
+ const userId = ctx.auth?.tokenData?.userId || ctx.session?.userId || "demo-user";
98
+
99
+ console.log(`\nInstallation successful for user: ${userId}`);
100
+ console.log(`Account: ${account.login}`);
101
+ console.log(`Repositories: ${repositories.length}`);
102
+
103
+ // Update user's GitHub installations
104
+ try {
105
+ const user = await ctx.repos.userRepo.getById(userId);
106
+ if (user) {
107
+ const installations = user.githubInstallations || [];
108
+ if (!installations.includes(installationId)) {
109
+ await ctx.repos.userRepo.updateOne(userId, {
110
+ githubInstallations: [...installations, installationId],
111
+ });
112
+ }
113
+ }
114
+ } catch (error) {
115
+ console.error("Failed to update user installations:", error);
116
+ }
117
+
118
+ return {
119
+ userId,
120
+ redirectUrl: "/dashboard/github",
121
+ };
122
+ },
123
+ }),
124
+ ],
125
+ });
126
+
127
+ await app.start();
128
+
129
+ console.log(`
130
+ =================================
131
+ JWT Auth Integration Example
132
+ =================================
133
+
134
+ This example shows OPTIONAL integration with JWT Auth Plugin.
135
+
136
+ The GitHub App Plugin works standalone without JWT Auth Plugin.
137
+
138
+ If JWT Auth Plugin is installed:
139
+ - Installation links to authenticated JWT user
140
+ - Endpoints can be protected with JWT authentication
141
+ - User context available via ctx.auth
142
+
143
+ If JWT Auth Plugin is NOT installed:
144
+ - Use your own authentication system
145
+ - Get userId from session, custom tokens, etc.
146
+ - Plugin works exactly the same
147
+
148
+ Available endpoints:
149
+ - GET /github/repos (protected if JWT Auth enabled)
150
+ - GET /github/installations (protected if JWT Auth enabled)
151
+
152
+ =================================
153
+ `);
154
+ }
155
+
156
+ // Handler: List user's GitHub repositories
157
+ // This handler works with or without JWT Auth Plugin
158
+ const GetUserGitHubRepos: GetHandler = async ({ ctx }) => {
159
+ // Get userId from JWT Auth context if available,
160
+ // otherwise from your custom auth system
161
+ const userId = ctx.auth?.tokenData?.userId || ctx.session?.userId || "demo-user";
162
+
163
+ try {
164
+ // Get user's GitHub installation
165
+ const installation = await ctx.plugins.githubApp.getInstallation(userId);
166
+
167
+ if (!installation) {
168
+ return {
169
+ status: 404,
170
+ data: {
171
+ error: "github-app-not-installed",
172
+ message: "GitHub App is not installed",
173
+ hint: "Install the GitHub App to access your repositories",
174
+ },
175
+ };
176
+ }
177
+
178
+ // Get API client
179
+ const client = await ctx.plugins.githubApp.getClient(installation.installationId);
180
+
181
+ // Fetch repositories
182
+ const repos = await client.getRepositories();
183
+
184
+ return {
185
+ status: 200,
186
+ data: {
187
+ user: {
188
+ id: userId,
189
+ // Include user data if JWT Auth is available
190
+ email: ctx.auth?.user?.email,
191
+ name: ctx.auth?.user?.name,
192
+ },
193
+ installation: {
194
+ id: installation.installationId,
195
+ account: installation.accountLogin,
196
+ type: installation.accountType,
197
+ },
198
+ repositories: repos.map((repo: any) => ({
199
+ id: repo.id,
200
+ name: repo.name,
201
+ fullName: repo.full_name,
202
+ private: repo.private,
203
+ url: repo.html_url,
204
+ })),
205
+ },
206
+ };
207
+ } catch (error: any) {
208
+ console.error("Failed to list repositories:", error);
209
+ return {
210
+ status: 500,
211
+ data: {
212
+ error: "repository-access-failed",
213
+ message: error.message,
214
+ },
215
+ };
216
+ }
217
+ };
218
+
219
+ export { GetUserGitHubRepos };
220
+
221
+ // Handler: Get user's GitHub installations
222
+ const GetUserInstallations: GetHandler = async ({ ctx }) => {
223
+ const userId = ctx.auth?.tokenData?.userId || ctx.session?.userId || "demo-user";
224
+
225
+ try {
226
+ // Get all installations for user
227
+ const installations = await ctx.plugins.githubApp.getInstallations(userId);
228
+
229
+ return {
230
+ status: 200,
231
+ data: {
232
+ user: {
233
+ id: userId,
234
+ email: ctx.auth?.user?.email,
235
+ },
236
+ installations: installations.map((inst) => ({
237
+ installationId: inst.installationId,
238
+ account: {
239
+ id: inst.accountId,
240
+ login: inst.accountLogin,
241
+ type: inst.accountType,
242
+ avatarUrl: inst.avatarUrl,
243
+ },
244
+ repositories: inst.repositories,
245
+ permissions: inst.permissions,
246
+ events: inst.events,
247
+ createdAt: inst.createdAt,
248
+ suspendedAt: inst.suspendedAt,
249
+ })),
250
+ total: installations.length,
251
+ },
252
+ };
253
+ } catch (error: any) {
254
+ console.error("Failed to get installations:", error);
255
+ return {
256
+ status: 500,
257
+ data: {
258
+ error: "installations-access-failed",
259
+ message: error.message,
260
+ },
261
+ };
262
+ }
263
+ };
264
+
265
+ export { GetUserInstallations };
266
+
267
+ // Example: Protected handler (if JWT Auth Plugin is enabled)
268
+ // If JWT Auth is not enabled, this will work without authentication
269
+ const GetProtectedResource: GetHandler = async ({ ctx }) => {
270
+ // This check only applies if JWT Auth Plugin is installed
271
+ if (!ctx.auth?.tokenData) {
272
+ // If no JWT Auth Plugin, allow access anyway
273
+ // Or implement your own authentication check
274
+ console.log("No JWT authentication, allowing access");
275
+ }
276
+
277
+ const userId = ctx.auth?.tokenData?.userId || "anonymous";
278
+
279
+ // Check if user has GitHub App installed
280
+ const installation = await ctx.plugins.githubApp.getInstallation(userId);
281
+
282
+ return {
283
+ status: 200,
284
+ data: {
285
+ message: "Protected resource accessed",
286
+ authenticated: !!ctx.auth?.tokenData,
287
+ userId,
288
+ hasGitHubApp: !!installation,
289
+ },
290
+ };
291
+ };
292
+
293
+ export { GetProtectedResource };
294
+
295
+ // Start application
296
+ start().catch((error) => {
297
+ console.error("Failed to start application:", error);
298
+ process.exit(1);
299
+ });
300
+
301
+ /*
302
+ * SUMMARY:
303
+ *
304
+ * The GitHub App Plugin is STANDALONE and does NOT require JWT Auth Plugin.
305
+ *
306
+ * Benefits of using with JWT Auth Plugin:
307
+ * - Automatic user context from JWT tokens
308
+ * - Protected endpoints with JWT authentication
309
+ * - Consistent authentication across all features
310
+ * - User data available via ctx.auth
311
+ *
312
+ * Benefits of using standalone:
313
+ * - No dependency on specific auth system
314
+ * - Works with any authentication mechanism
315
+ * - Simpler setup for custom auth systems
316
+ * - More flexibility in user management
317
+ *
318
+ * Choose the approach that fits your application architecture.
319
+ */
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@flink-app/github-app-plugin",
3
+ "version": "0.12.1-alpha.38",
4
+ "description": "Flink plugin for GitHub App integration with installation management and webhook handling",
5
+ "scripts": {
6
+ "test": "node --preserve-symlinks -r ts-node/register -- node_modules/jasmine/bin/jasmine --config=./spec/support/jasmine.json",
7
+ "test:watch": "nodemon --ext ts --exec 'jasmine-ts --config=./spec/support/jasmine.json'",
8
+ "prepare": "tsc --project tsconfig.dist.json",
9
+ "build": "tsc --project tsconfig.dist.json",
10
+ "watch": "tsc-watch --project tsconfig.dist.json"
11
+ },
12
+ "author": "joel@frost.se",
13
+ "license": "MIT",
14
+ "types": "dist/index.d.ts",
15
+ "main": "dist/index.js",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "peerDependencies": {
20
+ "mongodb": "^6.15.0"
21
+ },
22
+ "dependencies": {
23
+ "jsonwebtoken": "^9.0.2"
24
+ },
25
+ "devDependencies": {
26
+ "@flink-app/flink": "^0.12.1-alpha.35",
27
+ "@flink-app/test-utils": "^0.12.1-alpha.38",
28
+ "@types/jasmine": "^3.7.1",
29
+ "@types/jsonwebtoken": "^9.0.5",
30
+ "@types/node": "22.13.10",
31
+ "jasmine": "^3.7.0",
32
+ "jasmine-spec-reporter": "^7.0.0",
33
+ "mongodb": "6.15.0",
34
+ "mongodb-memory-server": "^10.2.3",
35
+ "nodemon": "^2.0.7",
36
+ "ts-node": "^9.1.1",
37
+ "tsc-watch": "^4.2.9",
38
+ "typescript": "5.4.5"
39
+ },
40
+ "gitHead": "bba579788683fe0729399a56152eba4974f29bb6"
41
+ }